diff options
378 files changed, 9522 insertions, 3089 deletions
diff --git a/apct-tests/perftests/blobstore/Android.bp b/apct-tests/perftests/blobstore/Android.bp index 9064b4494ed1..2590fe3d843f 100644 --- a/apct-tests/perftests/blobstore/Android.bp +++ b/apct-tests/perftests/blobstore/Android.bp @@ -29,7 +29,7 @@ android_test { "androidx.test.rules", "androidx.annotation_annotation", "apct-perftests-utils", - "ub-uiautomator", + "androidx.test.uiautomator_uiautomator", "collector-device-lib-platform", "androidx.benchmark_benchmark-macro", ], diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java index 0208dab33746..4e4780ff8948 100644 --- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java +++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/AtraceUtils.java @@ -21,10 +21,10 @@ import android.app.UiAutomation; import android.os.ParcelFileDescriptor; import android.perftests.utils.TraceMarkParser; import android.perftests.utils.TraceMarkParser.TraceMarkSlice; -import android.support.test.uiautomator.UiDevice; import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.uiautomator.UiDevice; import java.io.BufferedReader; import java.io.IOException; diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java index 03e5468a342a..3cd9f50aee6a 100644 --- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java +++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java @@ -25,12 +25,12 @@ import android.perftests.utils.ManualBenchmarkState; import android.perftests.utils.PerfManualStatusReporter; import android.perftests.utils.TraceMarkParser; import android.perftests.utils.TraceMarkParser.TraceMarkSlice; -import android.support.test.uiautomator.UiDevice; import android.util.DataUnit; import androidx.benchmark.macro.MacrobenchmarkScope; import androidx.test.filters.LargeTest; import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.uiautomator.UiDevice; import com.android.utils.blob.FakeBlobData; diff --git a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java index f56e1eea0f23..36174c6aad3d 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobSchedulerFrameworkInitializer.java @@ -20,6 +20,7 @@ import android.annotation.SystemApi; import android.app.JobSchedulerImpl; import android.app.SystemServiceRegistry; import android.app.tare.EconomyManager; +import android.app.tare.IEconomyManager; import android.content.Context; import android.os.DeviceIdleManager; import android.os.IDeviceIdleController; @@ -58,6 +59,7 @@ public class JobSchedulerFrameworkInitializer { Context.POWER_EXEMPTION_SERVICE, PowerExemptionManager.class, PowerExemptionManager::new); SystemServiceRegistry.registerStaticService( - Context.RESOURCE_ECONOMY_SERVICE, EconomyManager.class, EconomyManager::new); + Context.RESOURCE_ECONOMY_SERVICE, EconomyManager.class, + (b) -> new EconomyManager(IEconomyManager.Stub.asInterface(b))); } } diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java index 581ea7adc465..0bea028e6f50 100644 --- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java +++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java @@ -19,7 +19,9 @@ package android.app.tare; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; +import android.os.RemoteException; import android.util.Log; import java.lang.annotation.Retention; @@ -30,6 +32,7 @@ import java.lang.annotation.RetentionPolicy; * * @hide */ +@TestApi @SystemService(Context.RESOURCE_ECONOMY_SERVICE) public class EconomyManager { private static final String TAG = "TARE-" + EconomyManager.class.getSimpleName(); @@ -95,13 +98,17 @@ public class EconomyManager { } } - + /** @hide */ + @TestApi public static final int ENABLED_MODE_OFF = 0; + /** @hide */ public static final int ENABLED_MODE_ON = 1; /** * Go through the motions, tracking events, updating balances and other TARE state values, * but don't use TARE to affect actual device behavior. + * @hide */ + @TestApi public static final int ENABLED_MODE_SHADOW = 2; /** @hide */ @@ -114,6 +121,7 @@ public class EconomyManager { public @interface EnabledMode { } + /** @hide */ public static String enabledModeToString(@EnabledMode int mode) { switch (mode) { case ENABLED_MODE_OFF: return "ENABLED_MODE_OFF"; @@ -123,11 +131,18 @@ public class EconomyManager { } } + /** @hide */ + @TestApi public static final String KEY_ENABLE_TARE_MODE = "enable_tare_mode"; + /** @hide */ public static final String KEY_ENABLE_POLICY_ALARM = "enable_policy_alarm"; + /** @hide */ public static final String KEY_ENABLE_POLICY_JOB_SCHEDULER = "enable_policy_job"; + /** @hide */ public static final int DEFAULT_ENABLE_TARE_MODE = ENABLED_MODE_OFF; + /** @hide */ public static final boolean DEFAULT_ENABLE_POLICY_ALARM = true; + /** @hide */ public static final boolean DEFAULT_ENABLE_POLICY_JOB_SCHEDULER = true; // Keys for AlarmManager TARE factors @@ -612,4 +627,27 @@ public class EconomyManager { public static final long DEFAULT_JS_ACTION_JOB_MIN_RUNNING_BASE_PRICE_CAKES = arcToCake(1); /** @hide */ public static final long DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES = arcToCake(60); + + //////// APIs below //////// + + private final IEconomyManager mService; + + /** @hide */ + public EconomyManager(IEconomyManager service) { + mService = service; + } + + /** + * Returns the current enabled status of TARE. + * @hide + */ + @EnabledMode + @TestApi + public int getEnabledMode() { + try { + return mService.getEnabledMode(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } diff --git a/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl b/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl index bb150118ebb1..2be0db7a4c9f 100644 --- a/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl +++ b/apex/jobscheduler/framework/java/android/app/tare/IEconomyManager.aidl @@ -21,4 +21,5 @@ package android.app.tare; * {@hide} */ interface IEconomyManager { + int getEnabledMode(); } diff --git a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java index 64b242334a8a..fd8ddbcf3809 100644 --- a/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/job/JobSchedulerInternal.java @@ -69,8 +69,8 @@ public interface JobSchedulerInternal { * @return {@code true} if the given notification channel is associated with any user-initiated * jobs. */ - boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel, - int userId, String packageName); + boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs( + @NonNull String notificationChannel, int userId, @NonNull String packageName); /** * Report a snapshot of sync-related jobs back to the sync manager diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java index 0650ce3d141c..f779b4d96b45 100644 --- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java +++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java @@ -355,7 +355,7 @@ public class DeviceIdleController extends SystemService @GuardedBy("this") private boolean mHasGps; @GuardedBy("this") - private boolean mHasNetworkLocation; + private boolean mHasFusedLocation; @GuardedBy("this") private Location mLastGenericLocation; @GuardedBy("this") @@ -3782,12 +3782,14 @@ public class DeviceIdleController extends SystemService scheduleAlarmLocked(mConstants.LOCATING_TIMEOUT); LocationManager locationManager = mInjector.getLocationManager(); if (locationManager != null - && locationManager.getProvider(LocationManager.NETWORK_PROVIDER) != null) { - locationManager.requestLocationUpdates(mLocationRequest, - mGenericLocationListener, mHandler.getLooper()); + && locationManager.getProvider(LocationManager.FUSED_PROVIDER) != null) { + locationManager.requestLocationUpdates(LocationManager.FUSED_PROVIDER, + mLocationRequest, + AppSchedulingModuleThread.getExecutor(), + mGenericLocationListener); mLocating = true; } else { - mHasNetworkLocation = false; + mHasFusedLocation = false; } if (locationManager != null && locationManager.getProvider(LocationManager.GPS_PROVIDER) != null) { @@ -5301,9 +5303,10 @@ public class DeviceIdleController extends SystemService pw.print(" "); pw.print(mStationaryListeners.size()); pw.println(" stationary listeners registered"); } - pw.print(" mLocating="); pw.print(mLocating); pw.print(" mHasGps="); - pw.print(mHasGps); pw.print(" mHasNetwork="); - pw.print(mHasNetworkLocation); pw.print(" mLocated="); pw.println(mLocated); + pw.print(" mLocating="); pw.print(mLocating); + pw.print(" mHasGps="); pw.print(mHasGps); + pw.print(" mHasFused="); pw.print(mHasFusedLocation); + pw.print(" mLocated="); pw.println(mLocated); if (mLastGenericLocation != null) { pw.print(" mLastGenericLocation="); pw.println(mLastGenericLocation); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 8bd3d127c21b..b9b825c9f75c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -1925,16 +1925,14 @@ class JobConcurrencyManager { return null; } - @GuardedBy("mLock") boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, int userId, - String packageName) { + @NonNull String packageName) { return mNotificationCoordinator.isNotificationAssociatedWithAnyUserInitiatedJobs( notificationId, userId, packageName); } - @GuardedBy("mLock") - boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel, - int userId, String packageName) { + boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs( + @NonNull String notificationChannel, int userId, @NonNull String packageName) { return mNotificationCoordinator.isNotificationChannelAssociatedWithAnyUserInitiatedJobs( notificationChannel, userId, packageName); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java index f6e00ec24b33..d94674b5cab0 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobNotificationCoordinator.java @@ -27,9 +27,12 @@ import android.content.pm.UserPackage; import android.os.UserHandle; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.IntArray; import android.util.Slog; +import android.util.SparseArrayMap; import android.util.SparseSetArray; +import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.job.controllers.JobStatus; import com.android.server.notification.NotificationManagerInternal; @@ -38,6 +41,14 @@ class JobNotificationCoordinator { private static final String TAG = "JobNotificationCoordinator"; /** + * Local lock for independent objects like mUijNotifications and mUijNotificationChannels which + * don't depend on other JS objects such as JobServiceContext which require the global JS lock. + * + * Note: do <b>NOT</b> acquire the global lock while this one is held. + */ + private final Object mUijLock = new Object(); + + /** * Mapping of UserPackage -> {notificationId -> List<JobServiceContext>} to track which jobs * are associated with each app's notifications. */ @@ -49,6 +60,27 @@ class JobNotificationCoordinator { private final ArrayMap<JobServiceContext, NotificationDetails> mNotificationDetails = new ArrayMap<>(); + /** + * Mapping of userId -> {packageName, notificationIds} tracking which notifications + * associated with each app belong to user-initiated jobs. + * + * Note: this map can be accessed without holding the main JS lock, so that other services like + * NotificationManagerService can call into JS and verify associations. + */ + @GuardedBy("mUijLock") + private final SparseArrayMap<String, IntArray> mUijNotifications = new SparseArrayMap<>(); + + /** + * Mapping of userId -> {packageName, notificationChannels} tracking which notification channels + * associated with each app are hosting a user-initiated job notification. + * + * Note: this map can be accessed without holding the main JS lock, so that other services like + * NotificationManagerService can call into JS and verify associations. + */ + @GuardedBy("mUijLock") + private final SparseArrayMap<String, ArraySet<String>> mUijNotificationChannels = + new SparseArrayMap<>(); + private static final class NotificationDetails { @NonNull public final UserPackage userPackage; @@ -81,15 +113,24 @@ class JobNotificationCoordinator { int callingPid, int callingUid, int notificationId, @NonNull Notification notification, @JobService.JobEndNotificationPolicy int jobEndNotificationPolicy) { validateNotification(packageName, callingUid, notification, jobEndNotificationPolicy); + final JobStatus jobStatus = hostingContext.getRunningJobLocked(); final NotificationDetails oldDetails = mNotificationDetails.get(hostingContext); if (oldDetails != null && oldDetails.notificationId != notificationId) { // App is switching notification IDs. Remove association with the old one. - removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED); + removeNotificationAssociation(hostingContext, JobParameters.STOP_REASON_UNDEFINED, + jobStatus); } final int userId = UserHandle.getUserId(callingUid); - final JobStatus jobStatus = hostingContext.getRunningJobLocked(); if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) { notification.flags |= Notification.FLAG_USER_INITIATED_JOB; + synchronized (mUijLock) { + maybeCreateUijNotificationSetsLocked(userId, packageName); + final IntArray notificationIds = mUijNotifications.get(userId, packageName); + if (notificationIds.indexOf(notificationId) == -1) { + notificationIds.add(notificationId); + } + mUijNotificationChannels.get(userId, packageName).add(notification.getChannelId()); + } } final UserPackage userPackage = UserPackage.of(userId, packageName); final NotificationDetails details = new NotificationDetails( @@ -110,7 +151,7 @@ class JobNotificationCoordinator { } void removeNotificationAssociation(@NonNull JobServiceContext hostingContext, - @JobParameters.StopReason int stopReason) { + @JobParameters.StopReason int stopReason, JobStatus completedJob) { final NotificationDetails details = mNotificationDetails.remove(hostingContext); if (details == null) { return; @@ -121,10 +162,11 @@ class JobNotificationCoordinator { Slog.wtf(TAG, "Association data structures not in sync"); return; } - final String packageName = details.userPackage.packageName; final int userId = UserHandle.getUserId(details.appUid); + final String packageName = details.userPackage.packageName; + final int notificationId = details.notificationId; boolean stripUijFlag = true; - ArraySet<JobServiceContext> associatedContexts = associations.get(details.notificationId); + ArraySet<JobServiceContext> associatedContexts = associations.get(notificationId); if (associatedContexts == null || associatedContexts.isEmpty()) { // No more jobs using this notification. Apply the final job stop policy. // If the user attempted to stop the job/app, then always remove the notification @@ -133,23 +175,50 @@ class JobNotificationCoordinator { || stopReason == JobParameters.STOP_REASON_USER) { mNotificationManagerInternal.cancelNotification( packageName, packageName, details.appUid, details.appPid, /* tag */ null, - details.notificationId, userId); + notificationId, userId); stripUijFlag = false; } } else { // Strip the UIJ flag only if there are no other UIJs associated with the notification - stripUijFlag = !isNotificationAssociatedWithAnyUserInitiatedJobs( - details.notificationId, userId, packageName); + stripUijFlag = !isNotificationUsedForAnyUij(userId, packageName, notificationId); } if (stripUijFlag) { - // Strip the user-initiated job flag from the notification. mNotificationManagerInternal.removeUserInitiatedJobFlagFromNotification( - packageName, details.notificationId, userId); + packageName, notificationId, userId); + } + + // Clean up UIJ related objects if the just completed job was a UIJ + if (completedJob != null && completedJob.startedAsUserInitiatedJob) { + maybeDeleteNotificationIdAssociation(userId, packageName, notificationId); + maybeDeleteNotificationChannelAssociation( + userId, packageName, details.notificationChannel); } } boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, - int userId, String packageName) { + int userId, @NonNull String packageName) { + synchronized (mUijLock) { + final IntArray notifications = mUijNotifications.get(userId, packageName); + if (notifications != null) { + return notifications.indexOf(notificationId) != -1; + } + return false; + } + } + + boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs( + @NonNull String notificationChannel, int userId, @NonNull String packageName) { + synchronized (mUijLock) { + final ArraySet<String> channels = mUijNotificationChannels.get(userId, packageName); + if (channels != null) { + return channels.contains(notificationChannel); + } + return false; + } + } + + private boolean isNotificationUsedForAnyUij(int userId, String packageName, + int notificationId) { final UserPackage pkgDetails = UserPackage.of(userId, packageName); final SparseSetArray<JobServiceContext> associations = mCurrentAssociations.get(pkgDetails); if (associations == null) { @@ -170,8 +239,26 @@ class JobNotificationCoordinator { return false; } - boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs(String notificationChannel, - int userId, String packageName) { + private void maybeDeleteNotificationIdAssociation(int userId, String packageName, + int notificationId) { + if (isNotificationUsedForAnyUij(userId, packageName, notificationId)) { + return; + } + + // Safe to delete - no UIJs for this package are using this notification id + synchronized (mUijLock) { + final IntArray notifications = mUijNotifications.get(userId, packageName); + if (notifications != null) { + notifications.remove(notifications.indexOf(notificationId)); + if (notifications.size() == 0) { + mUijNotifications.delete(userId, packageName); + } + } + } + } + + private void maybeDeleteNotificationChannelAssociation(int userId, String packageName, + String notificationChannel) { for (int i = mNotificationDetails.size() - 1; i >= 0; i--) { final JobServiceContext jsc = mNotificationDetails.keyAt(i); final NotificationDetails details = mNotificationDetails.get(jsc); @@ -183,11 +270,31 @@ class JobNotificationCoordinator { && details.notificationChannel.equals(notificationChannel)) { final JobStatus jobStatus = jsc.getRunningJobLocked(); if (jobStatus != null && jobStatus.startedAsUserInitiatedJob) { - return true; + return; } } } - return false; + + // Safe to delete - no UIJs for this package are using this notification channel + synchronized (mUijLock) { + ArraySet<String> channels = mUijNotificationChannels.get(userId, packageName); + if (channels != null) { + channels.remove(notificationChannel); + if (channels.isEmpty()) { + mUijNotificationChannels.delete(userId, packageName); + } + } + } + } + + @GuardedBy("mUijLock") + private void maybeCreateUijNotificationSetsLocked(int userId, String packageName) { + if (mUijNotifications.get(userId, packageName) == null) { + mUijNotifications.add(userId, packageName, new IntArray()); + } + if (mUijNotificationChannels.get(userId, packageName) == null) { + mUijNotificationChannels.add(userId, packageName, new ArraySet<>()); + } } private void validateNotification(@NonNull String packageName, int callingUid, 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 3aec8ba39a35..6eeff82f1158 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -3713,26 +3713,22 @@ public class JobSchedulerService extends com.android.server.SystemService @Override public boolean isNotificationAssociatedWithAnyUserInitiatedJobs(int notificationId, - int userId, String packageName) { + int userId, @NonNull String packageName) { if (packageName == null) { return false; } - synchronized (mLock) { - return mConcurrencyManager.isNotificationAssociatedWithAnyUserInitiatedJobs( - notificationId, userId, packageName); - } + return mConcurrencyManager.isNotificationAssociatedWithAnyUserInitiatedJobs( + notificationId, userId, packageName); } @Override public boolean isNotificationChannelAssociatedWithAnyUserInitiatedJobs( - String notificationChannel, int userId, String packageName) { + @NonNull String notificationChannel, int userId, @NonNull String packageName) { if (packageName == null || notificationChannel == null) { return false; } - synchronized (mLock) { - return mConcurrencyManager.isNotificationChannelAssociatedWithAnyUserInitiatedJobs( - notificationChannel, userId, packageName); - } + return mConcurrencyManager.isNotificationChannelAssociatedWithAnyUserInitiatedJobs( + notificationChannel, userId, packageName); } @Override 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 44700c86efef..fb36cdec490f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -1464,7 +1464,8 @@ public final class JobServiceContext implements ServiceConnection { JobSchedulerEconomicPolicy.ACTION_JOB_TIMEOUT, String.valueOf(mRunningJob.getJobId())); } - mNotificationCoordinator.removeNotificationAssociation(this, reschedulingStopReason); + mNotificationCoordinator.removeNotificationAssociation(this, + reschedulingStopReason, completedJob); if (mWakeLock != null) { mWakeLock.release(); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java index eb43c38f76a3..ef634b565b65 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java +++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java @@ -92,13 +92,17 @@ public class ThermalStatusRestriction extends JobRestriction { final int priority = job.getEffectivePriority(); if (mThermalStatus >= HIGHER_PRIORITY_THRESHOLD) { // For moderate throttling: - // Only let expedited & user-initiated jobs run if: + // Let all user-initiated jobs run. + // Only let expedited jobs run if: // 1. They haven't previously run // 2. They're already running and aren't yet in overtime // Only let high priority jobs run if: // They are already running and aren't yet in overtime // Don't let any other job run. - if (job.shouldTreatAsExpeditedJob() || job.shouldTreatAsUserInitiatedJob()) { + if (job.shouldTreatAsUserInitiatedJob()) { + return false; + } + if (job.shouldTreatAsExpeditedJob()) { return job.getNumPreviousAttempts() > 0 || (mService.isCurrentlyRunningLocked(job) && mService.isJobInOvertimeLocked(job)); diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index 7f6a75e75397..c7070695ecc2 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -1351,6 +1351,11 @@ public class InternalResourceService extends SystemService { } @Override + public int getEnabledMode() { + return InternalResourceService.this.getEnabledMode(); + } + + @Override public int handleShellCommand(@NonNull ParcelFileDescriptor in, @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, @NonNull String[] args) { diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 699808156033..b6dc32a29f04 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -21,6 +21,7 @@ import android.annotation.UserIdInt; import android.app.backup.BackupManager; import android.app.backup.BackupManagerMonitor; import android.app.backup.BackupProgress; +import android.app.backup.BackupRestoreEventLogger; import android.app.backup.BackupTransport; import android.app.backup.IBackupManager; import android.app.backup.IBackupManagerMonitor; @@ -821,14 +822,22 @@ public class Bmgr { doRestorePackage(arg); } else { try { + @Monitor int monitor = Monitor.OFF; + long token = Long.parseLong(arg, 16); HashSet<String> filter = null; while ((arg = nextArg()) != null) { - if (filter == null) filter = new HashSet<String>(); - filter.add(arg); + if (arg.equals("--monitor")) { + monitor = Monitor.NORMAL; + } else if (arg.equals("--monitor-verbose")) { + monitor = Monitor.VERBOSE; + } else { + if (filter == null) filter = new HashSet<String>(); + filter.add(arg); + } } - doRestoreAll(userId, token, filter); + doRestoreAll(userId, token, filter, monitor); } catch (NumberFormatException e) { showUsage(); return; @@ -841,7 +850,8 @@ public class Bmgr { System.err.println("'restore <token> <package>'."); } - private void doRestoreAll(@UserIdInt int userId, long token, HashSet<String> filter) { + private void doRestoreAll(@UserIdInt int userId, long token, HashSet<String> filter, + @Monitor int monitorState) { RestoreObserver observer = new RestoreObserver(); try { @@ -852,8 +862,11 @@ public class Bmgr { return; } RestoreSet[] sets = null; - // TODO implement monitor here - int err = mRestore.getAvailableRestoreSets(observer, null); + BackupMonitor monitor = + (monitorState != Monitor.OFF) + ? new BackupMonitor(monitorState == Monitor.VERBOSE) + : null; + int err = mRestore.getAvailableRestoreSets(observer, monitor); if (err == 0) { observer.waitForCompletion(); sets = observer.sets; @@ -862,12 +875,12 @@ public class Bmgr { if (s.token == token) { System.out.println("Scheduling restore: " + s.name); if (filter == null) { - didRestore = (mRestore.restoreAll(token, observer, null) == 0); + didRestore = (mRestore.restoreAll(token, observer, monitor) == 0); } else { String[] names = new String[filter.size()]; filter.toArray(names); didRestore = (mRestore.restorePackages(token, observer, names, - null) == 0); + monitor) == 0); } break; } @@ -958,8 +971,8 @@ public class Bmgr { System.err.println(" bmgr list transports [-c]"); System.err.println(" bmgr list sets"); System.err.println(" bmgr transport WHICH|-c WHICH_COMPONENT"); - System.err.println(" bmgr restore TOKEN"); - System.err.println(" bmgr restore TOKEN PACKAGE..."); + System.err.println(" bmgr restore TOKEN [--monitor|--monitor-verbose]"); + System.err.println(" bmgr restore TOKEN PACKAGE... [--monitor|--monitor-verbose]"); System.err.println(" bmgr run"); System.err.println(" bmgr wipe TRANSPORT PACKAGE"); System.err.println(" bmgr fullbackup PACKAGE..."); @@ -1005,12 +1018,18 @@ public class Bmgr { System.err.println("restore operation from the currently active transport. It will deliver"); System.err.println("the restore set designated by the TOKEN argument to each application"); System.err.println("that had contributed data to that restore set."); + System.err.println(" --monitor flag prints monitor events (important events and errors"); + System.err.println(" encountered during restore)."); + System.err.println(" --monitor-verbose flag prints monitor events with all keys."); System.err.println(""); System.err.println("The 'restore' command when given a token and one or more package names"); System.err.println("initiates a restore operation of just those given packages from the restore"); System.err.println("set designated by the TOKEN argument. It is effectively the same as the"); System.err.println("'restore' operation supplying only a token, but applies a filter to the"); System.err.println("set of applications to be restored."); + System.err.println(" --monitor flag prints monitor events (important events and errors"); + System.err.println(" encountered during restore)."); + System.err.println(" --monitor-verbose flag prints monitor events with all keys."); System.err.println(""); System.err.println("The 'run' command causes any scheduled backup operation to be initiated"); System.err.println("immediately, without the usual waiting period for batching together"); @@ -1026,7 +1045,8 @@ public class Bmgr { System.err.println(""); System.err.println("The 'backupnow' command runs an immediate backup for one or more packages."); System.err.println(" --all flag runs backup for all eligible packages."); - System.err.println(" --monitor flag prints monitor events."); + System.err.println(" --monitor flag prints monitor events (important events and errors"); + System.err.println(" encountered during backup)."); System.err.println(" --monitor-verbose flag prints monitor events with all keys."); System.err.println("For each package it will run key/value or full data backup "); System.err.println("depending on the package's manifest declarations."); @@ -1076,6 +1096,37 @@ public class Bmgr { out.append("(v").append(version).append(")"); } } + if (event.containsKey(BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS)) { + ArrayList<BackupRestoreEventLogger.DataTypeResult> results = + event.getParcelableArrayList( + BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS, + BackupRestoreEventLogger.DataTypeResult.class); + out.append(", results = ["); + for (BackupRestoreEventLogger.DataTypeResult result : results) { + out.append("\n{\n\tdataType: "); + out.append(result.getDataType()); + out.append("\n\tsuccessCount: "); + out.append(result.getSuccessCount()); + out.append("\n\tfailCount: "); + out.append(result.getFailCount()); + out.append("\n\tmetadataHash: "); + out.append(Arrays.toString(result.getMetadataHash())); + + if (!result.getErrors().isEmpty()) { + out.append("\n\terrors: ["); + for (String error : result.getErrors().keySet()) { + out.append(error); + out.append(": "); + out.append(result.getErrors().get(error)); + out.append(";"); + } + out.append("]"); + } + out.append("\n}"); + + } + out.append("]"); + } if (mVerbose) { Set<String> remainingKeys = new ArraySet<>(event.keySet()); remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_ID); @@ -1083,6 +1134,7 @@ public class Bmgr { remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME); remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION); remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_VERSION); + remainingKeys.remove(BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS); if (!remainingKeys.isEmpty()) { out.append(", other keys ="); for (String key : remainingKeys) { @@ -1192,6 +1244,8 @@ public class Bmgr { return "NO_PACKAGES"; case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL: return "TRANSPORT_IS_NULL"; + case BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS: + return "AGENT_LOGGING_RESULTS"; default: return "UNKNOWN_ID"; } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 5999e3c4a175..2d9a99cf0d8c 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -777,6 +777,17 @@ package android.app.prediction { } +package android.app.tare { + + public class EconomyManager { + method public int getEnabledMode(); + field public static final int ENABLED_MODE_OFF = 0; // 0x0 + field public static final int ENABLED_MODE_SHADOW = 2; // 0x2 + field public static final String KEY_ENABLE_TARE_MODE = "enable_tare_mode"; + } + +} + package android.app.usage { public class StorageStatsManager { diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index c131ce574d2c..e31486f18dbf 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -2354,8 +2354,7 @@ public class Instrumentation { return mUiAutomation; } if (mustCreateNewAutomation) { - mUiAutomation = new UiAutomation(getTargetContext().getMainLooper(), - mUiAutomationConnection); + mUiAutomation = new UiAutomation(getTargetContext(), mUiAutomationConnection); } else { mUiAutomation.disconnect(); } diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java index 658e08444006..247d5bc77ffb 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -16,6 +16,8 @@ package android.app; +import static android.view.Display.DEFAULT_DISPLAY; + import android.accessibilityservice.AccessibilityGestureEvent; import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityService.Callbacks; @@ -30,6 +32,7 @@ import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; import android.graphics.Bitmap; import android.graphics.Point; import android.graphics.Rect; @@ -45,6 +48,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; +import android.os.UserManager; import android.util.ArraySet; import android.util.DebugUtils; import android.util.Log; @@ -69,8 +73,10 @@ import android.view.accessibility.IAccessibilityInteractionConnection; import android.view.inputmethod.EditorInfo; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback; import com.android.internal.inputmethod.RemoteAccessibilityInputConnection; +import com.android.internal.util.Preconditions; import com.android.internal.util.function.pooled.PooledLambda; import libcore.io.IoUtils; @@ -202,6 +208,8 @@ public final class UiAutomation { private final IUiAutomationConnection mUiAutomationConnection; + private final int mDisplayId; + private HandlerThread mRemoteCallbackThread; private IAccessibilityServiceClient mClient; @@ -261,24 +269,49 @@ public final class UiAutomation { /** * Creates a new instance that will handle callbacks from the accessibility + * layer on the thread of the provided context main looper and perform requests for privileged + * operations on the provided connection, and filtering display-related features to the display + * associated with the context (or the user running the test, on devices that + * {@link UserManager#isVisibleBackgroundUsersSupported() support visible background users}). + * + * @param context the context associated with the automation + * @param connection The connection for performing privileged operations. + * + * @hide + */ + public UiAutomation(Context context, IUiAutomationConnection connection) { + this(getDisplayId(context), context.getMainLooper(), connection); + } + + /** + * Creates a new instance that will handle callbacks from the accessibility * layer on the thread of the provided looper and perform requests for privileged * operations on the provided connection. * * @param looper The looper on which to execute accessibility callbacks. * @param connection The connection for performing privileged operations. * + * @deprecated use {@link #UiAutomation(Context, IUiAutomationConnection)} instead + * * @hide */ + @Deprecated @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public UiAutomation(Looper looper, IUiAutomationConnection connection) { - if (looper == null) { - throw new IllegalArgumentException("Looper cannot be null!"); - } - if (connection == null) { - throw new IllegalArgumentException("Connection cannot be null!"); - } + this(DEFAULT_DISPLAY, looper, connection); + Log.w(LOG_TAG, "Created with deprecatead constructor, assumes DEFAULT_DISPLAY"); + } + + private UiAutomation(int displayId, Looper looper, IUiAutomationConnection connection) { + Preconditions.checkArgument(looper != null, "Looper cannot be null!"); + Preconditions.checkArgument(connection != null, "Connection cannot be null!"); + mLocalCallbackHandler = new Handler(looper); mUiAutomationConnection = connection; + mDisplayId = displayId; + + Log.i(LOG_TAG, "Initialized for user " + Process.myUserHandle().getIdentifier() + + " on display " + mDisplayId); } /** @@ -719,8 +752,14 @@ public final class UiAutomation { } /** - * Gets the windows on the screen of the default display. This method returns only the windows - * that a sighted user can interact with, as opposed to all windows. + * Gets the windows on the screen associated with the {@link UiAutomation} context (usually the + * {@link android.view.Display#DEFAULT_DISPLAY default display). + * + * <p> + * This method returns only the windows that a sighted user can interact with, as opposed to + * all windows. + + * <p> * For example, if there is a modal dialog shown and the user cannot touch * anything behind it, then only the modal window will be reported * (assuming it is the top one). For convenience the returned windows @@ -730,21 +769,23 @@ public final class UiAutomation { * <strong>Note:</strong> In order to access the windows you have to opt-in * to retrieve the interactive windows by setting the * {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS} flag. - * </p> * * @return The windows if there are windows such, otherwise an empty list. * @throws IllegalStateException If the connection to the accessibility subsystem is not * established. */ public List<AccessibilityWindowInfo> getWindows() { + if (DEBUG) { + Log.d(LOG_TAG, "getWindows(): returning windows for display " + mDisplayId); + } final int connectionId; synchronized (mLock) { throwIfNotConnectedLocked(); connectionId = mConnectionId; } // Calling out without a lock held. - return AccessibilityInteractionClient.getInstance() - .getWindows(connectionId); + return AccessibilityInteractionClient.getInstance().getWindowsOnDisplay(connectionId, + mDisplayId); } /** @@ -1112,8 +1153,10 @@ public final class UiAutomation { * @return The screenshot bitmap on success, null otherwise. */ public Bitmap takeScreenshot() { - Display display = DisplayManagerGlobal.getInstance() - .getRealDisplay(Display.DEFAULT_DISPLAY); + if (DEBUG) { + Log.d(LOG_TAG, "Taking screenshot of display " + mDisplayId); + } + Display display = DisplayManagerGlobal.getInstance().getRealDisplay(mDisplayId); Point displaySize = new Point(); display.getRealSize(displaySize); @@ -1126,10 +1169,12 @@ public final class UiAutomation { screenShot = mUiAutomationConnection.takeScreenshot( new Rect(0, 0, displaySize.x, displaySize.y)); if (screenShot == null) { + Log.e(LOG_TAG, "mUiAutomationConnection.takeScreenshot() returned null for display " + + mDisplayId); return null; } } catch (RemoteException re) { - Log.e(LOG_TAG, "Error while taking screenshot!", re); + Log.e(LOG_TAG, "Error while taking screenshot of display " + mDisplayId, re); return null; } @@ -1509,6 +1554,14 @@ public final class UiAutomation { return executeShellCommandInternal(command, true /* includeStderr */); } + /** + * @hide + */ + @VisibleForTesting + public int getDisplayId() { + return mDisplayId; + } + private ParcelFileDescriptor[] executeShellCommandInternal( String command, boolean includeStderr) { warnIfBetterCommand(command); @@ -1564,6 +1617,7 @@ public final class UiAutomation { final StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("UiAutomation@").append(Integer.toHexString(hashCode())); stringBuilder.append("[id=").append(mConnectionId); + stringBuilder.append(", displayId=").append(mDisplayId); stringBuilder.append(", flags=").append(mFlags); stringBuilder.append("]"); return stringBuilder.toString(); @@ -1601,6 +1655,55 @@ public final class UiAutomation { return (mFlags & UiAutomation.FLAG_DONT_USE_ACCESSIBILITY) == 0; } + /** + * Gets the display id associated with the UiAutomation context. + * + * <p><b>NOTE: </b> must be a static method because it's called from a constructor to call + * another one. + */ + private static int getDisplayId(Context context) { + Preconditions.checkArgument(context != null, "Context cannot be null!"); + + UserManager userManager = context.getSystemService(UserManager.class); + // TODO(b/255426725): given that this is a temporary solution until a11y supports multiple + // users, the display is only set on devices that support that + if (!userManager.isVisibleBackgroundUsersSupported()) { + return DEFAULT_DISPLAY; + } + + int displayId = context.getDisplayId(); + if (displayId == Display.INVALID_DISPLAY) { + // Shouldn't happen, but we better handle it + Log.e(LOG_TAG, "UiAutomation created UI context with invalid display id, assuming it's" + + " running in the display assigned to the user"); + return getMainDisplayIdAssignedToUser(context, userManager); + } + + if (displayId != DEFAULT_DISPLAY) { + if (DEBUG) { + Log.d(LOG_TAG, "getDisplayId(): returning context's display (" + displayId + ")"); + } + // Context is explicitly setting the display, so we respect that... + return displayId; + } + // ...otherwise, we need to get the display the test's user is running on + int userDisplayId = getMainDisplayIdAssignedToUser(context, userManager); + if (DEBUG) { + Log.d(LOG_TAG, "getDisplayId(): returning user's display (" + userDisplayId + ")"); + } + return userDisplayId; + } + + private static int getMainDisplayIdAssignedToUser(Context context, UserManager userManager) { + if (!userManager.isUserVisible()) { + // Should also not happen, but ... + Log.e(LOG_TAG, "User (" + context.getUserId() + ") is not visible, using " + + "DEFAULT_DISPLAY"); + return DEFAULT_DISPLAY; + } + return userManager.getMainDisplayIdAssignedToUser(); + } + private class IAccessibilityServiceClientImpl extends IAccessibilityServiceClientWrapper { public IAccessibilityServiceClientImpl(Looper looper, int generationId) { @@ -1621,6 +1724,7 @@ public final class UiAutomation { if (DEBUG) { Log.d(LOG_TAG, "init(): connectionId=" + connectionId + ", windowToken=" + windowToken + ", user=" + Process.myUserHandle() + + ", UiAutomation.mDisplay=" + UiAutomation.this.mDisplayId + ", mGenerationId=" + mGenerationId + ", UiAutomation.mGenerationId=" + UiAutomation.this.mGenerationId); diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java index 13e800e38cca..d96a9d104ec2 100644 --- a/core/java/android/app/UiAutomationConnection.java +++ b/core/java/android/app/UiAutomationConnection.java @@ -22,6 +22,7 @@ import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.IAccessibilityServiceClient; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UserIdInt; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.Bitmap; @@ -117,7 +118,8 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { throw new IllegalStateException("Already connected."); } mOwningUid = Binder.getCallingUid(); - registerUiTestAutomationServiceLocked(client, flags); + registerUiTestAutomationServiceLocked(client, + Binder.getCallingUserHandle().getIdentifier(), flags); storeRotationStateLocked(); } } @@ -553,7 +555,7 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { } private void registerUiTestAutomationServiceLocked(IAccessibilityServiceClient client, - int flags) { + @UserIdInt int userId, int flags) { IAccessibilityManager manager = IAccessibilityManager.Stub.asInterface( ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); final AccessibilityServiceInfo info = new AccessibilityServiceInfo(); @@ -571,10 +573,11 @@ public final class UiAutomationConnection extends IUiAutomationConnection.Stub { try { // Calling out with a lock held is fine since if the system // process is gone the client calling in will be killed. - manager.registerUiTestAutomationService(mToken, client, info, flags); + manager.registerUiTestAutomationService(mToken, client, info, userId, flags); mClient = client; } catch (RemoteException re) { - throw new IllegalStateException("Error while registering UiTestAutomationService.", re); + throw new IllegalStateException("Error while registering UiTestAutomationService for " + + "user " + userId + ".", re); } } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 2aead3c22deb..72a3f6c0bc88 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -51,9 +51,11 @@ import android.view.Display; import android.view.Surface; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -81,8 +83,10 @@ public final class DisplayManager { private final DisplayManagerGlobal mGlobal; private final Object mLock = new Object(); - private final SparseArray<Display> mDisplays = new SparseArray<Display>(); + @GuardedBy("mLock") + private final WeakDisplayCache mDisplayCache = new WeakDisplayCache(); + @GuardedBy("mLock") private final ArrayList<Display> mTempDisplays = new ArrayList<Display>(); /** @@ -684,6 +688,7 @@ public final class DisplayManager { } } + @GuardedBy("mLock") private void addAllDisplaysLocked(ArrayList<Display> displays, int[] displayIds) { for (int i = 0; i < displayIds.length; i++) { Display display = getOrCreateDisplayLocked(displayIds[i], true /*assumeValid*/); @@ -693,6 +698,7 @@ public final class DisplayManager { } } + @GuardedBy("mLock") private void addDisplaysLocked( ArrayList<Display> displays, int[] displayIds, int matchType, int flagMask) { for (int displayId : displayIds) { @@ -709,8 +715,9 @@ public final class DisplayManager { } } + @GuardedBy("mLock") private Display getOrCreateDisplayLocked(int displayId, boolean assumeValid) { - Display display = mDisplays.get(displayId); + Display display = mDisplayCache.get(displayId); if (display == null) { // TODO: We cannot currently provide any override configurations for metrics on displays // other than the display the context is associated with. @@ -719,7 +726,7 @@ public final class DisplayManager { display = mGlobal.getCompatibleDisplay(displayId, resources); if (display != null) { - mDisplays.put(displayId, display); + mDisplayCache.put(display); } } else if (!assumeValid && !display.isValid()) { display = null; @@ -1767,4 +1774,57 @@ public final class DisplayManager { */ String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data"; } + + /** + * Helper class to maintain cache of weak references to Display instances. + * + * Note this class is not thread-safe, so external synchronization is needed if accessed + * concurrently. + */ + private static final class WeakDisplayCache { + private final SparseArray<WeakReference<Display>> mDisplayCache = new SparseArray<>(); + + /** + * Return cached {@link Display} instance for the provided display id. + * + * @param displayId - display id of the requested {@link Display} instance. + * @return cached {@link Display} instance or null + */ + Display get(int displayId) { + WeakReference<Display> wrDisplay = mDisplayCache.get(displayId); + if (wrDisplay == null) { + return null; + } + return wrDisplay.get(); + } + + /** + * Insert new {@link Display} instance in the cache. This replaced the previously cached + * {@link Display} instance, if there's already one with the same display id. + * + * @param display - Display instance to cache. + */ + void put(Display display) { + removeStaleEntries(); + mDisplayCache.put(display.getDisplayId(), new WeakReference<>(display)); + } + + /** + * Evict gc-ed entries from the cache. + */ + private void removeStaleEntries() { + ArrayList<Integer> staleEntriesIndices = new ArrayList(); + for (int i = 0; i < mDisplayCache.size(); i++) { + if (mDisplayCache.valueAt(i).get() == null) { + staleEntriesIndices.add(i); + } + } + + for (int i = 0; i < staleEntriesIndices.size(); i++) { + // removeAt call to SparseArray doesn't compact the underlying array + // so the indices stay valid even after removal. + mDisplayCache.removeAt(staleEntriesIndices.get(i)); + } + } + } } diff --git a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl index dcc336966810..466373030c78 100644 --- a/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl +++ b/core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl @@ -42,12 +42,6 @@ oneway interface IRecognitionStatusCallback { void onGenericSoundTriggerDetected(in SoundTrigger.GenericRecognitionEvent recognitionEvent); /** - * Called when the detection fails due to an error. - * - * @param status The error code that was seen. - */ - void onError(int status); - /** * Called when the recognition is paused temporarily for some reason. */ void onRecognitionPaused(); @@ -55,4 +49,28 @@ oneway interface IRecognitionStatusCallback { * Called when the recognition is resumed after it was temporarily paused. */ void onRecognitionResumed(); + + // Error callbacks to follow + /** + * Called when this recognition has been preempted by another. + */ + void onPreempted(); + + /** + * Called when the underlying ST module service has died. + */ + void onModuleDied(); + + /** + * Called when the service failed to gracefully resume recognition following a pause. + * @param status - The received error code. + */ + void onResumeFailed(int status); + + /** + * Called when the service failed to pause recognition when required. + * TODO(b/276507281) Remove. This should never happen, so we should abort instead. + * @param status - The received error code. + */ + void onPauseFailed(int status); } diff --git a/core/java/android/inputmethodservice/InkWindow.java b/core/java/android/inputmethodservice/InkWindow.java index 15ed45041d32..24d1c9577f82 100644 --- a/core/java/android/inputmethodservice/InkWindow.java +++ b/core/java/android/inputmethodservice/InkWindow.java @@ -195,6 +195,7 @@ final class InkWindow extends PhoneWindow { Objects.requireNonNull(decor); final ViewRootImpl viewRoot = decor.getViewRootImpl(); Objects.requireNonNull(viewRoot); - viewRoot.enqueueInputEvent(event); + // The view root will own the event that we enqueue, so provide a copy of the event. + viewRoot.enqueueInputEvent(MotionEvent.obtain(event)); } } diff --git a/core/java/android/nfc/tech/NdefFormatable.java b/core/java/android/nfc/tech/NdefFormatable.java index f19d30258393..2240fe7f7d3b 100644 --- a/core/java/android/nfc/tech/NdefFormatable.java +++ b/core/java/android/nfc/tech/NdefFormatable.java @@ -124,6 +124,9 @@ public final class NdefFormatable extends BasicTagTechnology { try { int serviceHandle = mTag.getServiceHandle(); INfcTag tagService = mTag.getTagService(); + if (tagService == null) { + throw new IOException(); + } int errorCode = tagService.formatNdef(serviceHandle, MifareClassic.KEY_DEFAULT); switch (errorCode) { case ErrorCodes.SUCCESS: diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 9f9c2222f9d9..7383e633fb93 100755 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -493,7 +493,7 @@ public class Build { * @hide */ @TestApi - public static final int RESOURCES_SDK_INT = SDK_INT + ACTIVE_CODENAMES.length; + public static final int RESOURCES_SDK_INT = SDK_INT; /** * The current lowest supported value of app target SDK. Applications targeting diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java index 63259edeae7d..218ecc8c5571 100644 --- a/core/java/android/os/image/DynamicSystemClient.java +++ b/core/java/android/os/image/DynamicSystemClient.java @@ -202,6 +202,13 @@ public class DynamicSystemClient { public static final String ACTION_NOTIFY_IF_IN_USE = "android.os.image.action.NOTIFY_IF_IN_USE"; + /** + * Intent action: hide notifications about the status of {@code DynamicSystem}. + * @hide + */ + public static final String ACTION_HIDE_NOTIFICATION = + "android.os.image.action.HIDE_NOTIFICATION"; + /* * Intent Keys */ @@ -217,6 +224,28 @@ public class DynamicSystemClient { */ public static final String KEY_USERDATA_SIZE = "KEY_USERDATA_SIZE"; + /** + * Intent key: Whether to enable DynamicSystem immediately after installation is done. + * Note this will reboot the device automatically. + * @hide + */ + public static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED"; + + /** + * Intent key: Whether to leave DynamicSystem on device reboot. + * False indicates a sticky mode where device stays in DynamicSystem across reboots. + * @hide + */ + public static final String KEY_ONE_SHOT = "KEY_ONE_SHOT"; + + /** + * Intent key: Whether to use default strings when showing the dialog that prompts + * user for device credentials. + * False indicates using the custom strings provided by {@code DynamicSystem}. + * @hide + */ + public static final String KEY_KEYGUARD_USE_DEFAULT_STRINGS = + "KEY_KEYGUARD_USE_DEFAULT_STRINGS"; private static class IncomingHandler extends Handler { private final WeakReference<DynamicSystemClient> mWeakClient; diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7cb959dee245..d75d18643d9e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1748,6 +1748,21 @@ public final class Settings { public static final String ACTION_DREAM_SETTINGS = "android.settings.DREAM_SETTINGS"; /** + * Activity Action: Show Communal settings. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: Nothing. + * <p> + * Output: Nothing. + * + * @hide + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_COMMUNAL_SETTING = "android.settings.COMMUNAL_SETTINGS"; + + /** * Activity Action: Show Notification assistant settings. * <p> * In some cases, a matching Activity may not exist, so ensure you @@ -2585,7 +2600,7 @@ public final class Settings { * <p> * To start an activity with this intent, apps should set the wellbeing package explicitly in * the intent together with this action. The wellbeing package is defined in - * {@code com.android.internal.R.string.config_defaultWellbeingPackage}. + * {@code com.android.internal.R.string.config_systemWellbeing}. * <p> * Output: Nothing * diff --git a/core/java/android/service/credentials/CredentialProviderInfoFactory.java b/core/java/android/service/credentials/CredentialProviderInfoFactory.java index d2a4a660452c..fb2f4ad2abe6 100644 --- a/core/java/android/service/credentials/CredentialProviderInfoFactory.java +++ b/core/java/android/service/credentials/CredentialProviderInfoFactory.java @@ -42,7 +42,6 @@ import android.os.RemoteException; import android.os.UserHandle; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Log; import android.util.Slog; import android.util.Xml; @@ -54,7 +53,6 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -181,7 +179,8 @@ public final class CredentialProviderInfoFactory { if (disableSystemAppVerificationForTests) { Bundle metadata = serviceInfo.metaData; if (metadata == null) { - Slog.e(TAG, "isValidSystemProvider - metadata is null: " + serviceInfo); + Slog.w(TAG, "metadata is null while reading " + + "TEST_SYSTEM_PROVIDER_META_DATA_KEY: " + serviceInfo); return false; } return metadata.getBoolean( @@ -200,7 +199,7 @@ public final class CredentialProviderInfoFactory { // 1. Get the metadata for the service. final Bundle metadata = serviceInfo.metaData; if (metadata == null) { - Log.i(TAG, "populateMetadata - metadata is null"); + Slog.w(TAG, "Metadata is null for provider: " + serviceInfo.getComponentName()); return builder; } @@ -209,12 +208,13 @@ public final class CredentialProviderInfoFactory { try { resources = pm.getResourcesForApplication(serviceInfo.applicationInfo); } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Failed to get app resources", e); + Slog.e(TAG, "Failed to get app resources", e); } // 3. Stop if we are missing data. - if (metadata == null || resources == null) { - Log.i(TAG, "populateMetadata - resources is null"); + if (resources == null) { + Slog.w(TAG, "Resources are null for the serviceInfo being processed: " + + serviceInfo.getComponentName()); return builder; } @@ -222,7 +222,7 @@ public final class CredentialProviderInfoFactory { try { builder = extractXmlMetadata(context, builder, serviceInfo, pm, resources); } catch (Exception e) { - Log.e(TAG, "Failed to get XML metadata", e); + Slog.e(TAG, "Failed to get XML metadata", e); } return builder; @@ -259,7 +259,7 @@ public final class CredentialProviderInfoFactory { afsAttributes.getString( R.styleable.CredentialProvider_settingsSubtitle)); } catch (Exception e) { - Log.e(TAG, "Failed to get XML attr", e); + Slog.e(TAG, "Failed to get XML attr", e); } finally { if (afsAttributes != null) { afsAttributes.recycle(); @@ -267,10 +267,10 @@ public final class CredentialProviderInfoFactory { } builder.addCapabilities(parseXmlProviderOuterCapabilities(parser, resources)); } else { - Log.e(TAG, "Meta-data does not start with credential-provider-service tag"); + Slog.w(TAG, "Meta-data does not start with credential-provider-service tag"); } } catch (IOException | XmlPullParserException e) { - Log.e(TAG, "Error parsing credential provider service meta-data", e); + Slog.e(TAG, "Error parsing credential provider service meta-data", e); } return builder; @@ -329,7 +329,7 @@ public final class CredentialProviderInfoFactory { return si; } } catch (RemoteException e) { - Slog.v(TAG, e.getMessage()); + Slog.e(TAG, "Unable to get serviceInfo", e); } throw new PackageManager.NameNotFoundException(serviceComponent.toString()); } @@ -377,10 +377,8 @@ public final class CredentialProviderInfoFactory { } services.add(serviceInfo); - } catch (SecurityException e) { - Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e); - } catch (PackageManager.NameNotFoundException e) { - Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e); + } catch (SecurityException | PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Error getting info for " + serviceInfo, e); } } return services; @@ -432,7 +430,7 @@ public final class CredentialProviderInfoFactory { return pp; } catch (SecurityException e) { // If the current user is not enrolled in DPM then this can throw a security error. - Log.e(TAG, "Failed to get device policy: " + e); + Slog.e(TAG, "Failed to get device policy: " + e); } return null; @@ -593,7 +591,7 @@ public final class CredentialProviderInfoFactory { for (ResolveInfo resolveInfo : resolveInfos) { final ServiceInfo serviceInfo = resolveInfo.serviceInfo; if (serviceInfo == null) { - Log.i(TAG, "No serviceInfo found for resolveInfo so skipping this provider"); + Slog.d(TAG, "No serviceInfo found for resolveInfo, so skipping provider"); continue; } @@ -608,10 +606,8 @@ public final class CredentialProviderInfoFactory { if (!cpi.isSystemProvider()) { services.add(cpi); } - } catch (SecurityException e) { - Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e); } catch (Exception e) { - Slog.e(TAG, "Error getting info for " + serviceInfo + ": " + e); + Slog.e(TAG, "Error getting info for " + serviceInfo, e); } } return services; diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java index e87333fe4941..53a5fd5c634d 100644 --- a/core/java/android/service/credentials/CredentialProviderService.java +++ b/core/java/android/service/credentials/CredentialProviderService.java @@ -226,7 +226,7 @@ public abstract class CredentialProviderService extends Service { if (SERVICE_INTERFACE.equals(intent.getAction())) { return mInterface.asBinder(); } - Log.i(TAG, "Failed to bind with intent: " + intent); + Log.d(TAG, "Failed to bind with intent: " + intent); return null; } diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java index dd5373ff8c38..82571db469be 100644 --- a/core/java/android/service/dreams/DreamManagerInternal.java +++ b/core/java/android/service/dreams/DreamManagerInternal.java @@ -80,10 +80,10 @@ public abstract class DreamManagerInternal { */ public interface DreamManagerStateListener { /** - * Called when keep dreaming when undocked has changed. + * Called when keep dreaming when plug has changed. * * @param keepDreaming True if the current dream should continue when undocking. */ - void onKeepDreamingWhenUndockedChanged(boolean keepDreaming); + void onKeepDreamingWhenUnpluggingChanged(boolean keepDreaming); } } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 8688a18880b7..24c96eae03cb 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -1573,16 +1573,6 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } @Override - public void onError(int status) { - Slog.i(TAG, "onError: " + status); - // TODO(b/271534248): This is a workaround before the sound trigger uses the new error - // method. - Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE, - new SoundTriggerFailure(SoundTriggerFailure.ERROR_CODE_UNKNOWN, - "Sound trigger error")).sendToTarget(); - } - - @Override public void onHotwordDetectionServiceFailure( HotwordDetectionServiceFailure hotwordDetectionServiceFailure) { Slog.v(TAG, "onHotwordDetectionServiceFailure: " + hotwordDetectionServiceFailure); @@ -1605,6 +1595,12 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { } @Override + public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) { + Message.obtain(mHandler, MSG_DETECTION_SOUND_TRIGGER_FAILURE, + Objects.requireNonNull(soundTriggerFailure)).sendToTarget(); + } + + @Override public void onUnknownFailure(String errorMessage) throws RemoteException { Slog.v(TAG, "onUnknownFailure: " + errorMessage); Message.obtain(mHandler, MSG_DETECTION_UNKNOWN_FAILURE, diff --git a/core/java/android/service/voice/SoftwareHotwordDetector.java b/core/java/android/service/voice/SoftwareHotwordDetector.java index eac7aee43859..7ab4fafcf312 100644 --- a/core/java/android/service/voice/SoftwareHotwordDetector.java +++ b/core/java/android/service/voice/SoftwareHotwordDetector.java @@ -234,14 +234,6 @@ class SoftwareHotwordDetector extends AbstractDetector { } @Override - public void onError(int status) throws RemoteException { - if (DEBUG) { - Slog.i(TAG, "Ignored #onError (" + status + ") event"); - } - // TODO: Check if we still need to implement this method with DetectorFailure mechanism. - } - - @Override public void onHotwordDetectionServiceFailure( HotwordDetectionServiceFailure hotwordDetectionServiceFailure) throws RemoteException { @@ -265,6 +257,13 @@ class SoftwareHotwordDetector extends AbstractDetector { } @Override + public void onSoundTriggerFailure(SoundTriggerFailure onSoundTriggerFailure) + throws RemoteException { + // It should never be called here. + Slog.wtf(TAG, "Unexpected STFailure in software detector: " + onSoundTriggerFailure); + } + + @Override public void onUnknownFailure(String errorMessage) throws RemoteException { Slog.v(TAG, "onUnknownFailure: " + errorMessage); Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> { diff --git a/core/java/android/service/voice/SoundTriggerFailure.java b/core/java/android/service/voice/SoundTriggerFailure.java index 5560800a373f..2ce5e5da4724 100644 --- a/core/java/android/service/voice/SoundTriggerFailure.java +++ b/core/java/android/service/voice/SoundTriggerFailure.java @@ -73,18 +73,28 @@ public final class SoundTriggerFailure implements Parcelable { @Retention(RetentionPolicy.SOURCE) public @interface SoundTriggerErrorCode {} - private int mErrorCode = ERROR_CODE_UNKNOWN; - private String mErrorMessage = "Unknown"; + private final int mErrorCode; + private final String mErrorMessage; /** * @hide */ @TestApi - public SoundTriggerFailure(int errorCode, @NonNull String errorMessage) { + public SoundTriggerFailure(@SoundTriggerErrorCode int errorCode, + @NonNull String errorMessage) { if (TextUtils.isEmpty(errorMessage)) { throw new IllegalArgumentException("errorMessage is empty or null."); } - mErrorCode = errorCode; + switch (errorCode) { + case ERROR_CODE_UNKNOWN: + case ERROR_CODE_MODULE_DIED: + case ERROR_CODE_RECOGNITION_RESUME_FAILED: + case ERROR_CODE_UNEXPECTED_PREEMPTION: + mErrorCode = errorCode; + break; + default: + throw new IllegalArgumentException("Invalid ErrorCode: " + errorCode); + } mErrorMessage = errorMessage; } @@ -110,13 +120,14 @@ public final class SoundTriggerFailure implements Parcelable { @FailureSuggestedAction.FailureSuggestedActionDef public int getSuggestedAction() { switch (mErrorCode) { + case ERROR_CODE_UNKNOWN: case ERROR_CODE_MODULE_DIED: case ERROR_CODE_UNEXPECTED_PREEMPTION: return FailureSuggestedAction.RECREATE_DETECTOR; case ERROR_CODE_RECOGNITION_RESUME_FAILED: return FailureSuggestedAction.RESTART_RECOGNITION; default: - return FailureSuggestedAction.NONE; + throw new AssertionError("Unexpected error code"); } } diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java index b4f5ff1046ae..93b7964705ba 100644 --- a/core/java/android/service/voice/VisualQueryDetector.java +++ b/core/java/android/service/voice/VisualQueryDetector.java @@ -391,12 +391,6 @@ public class VisualQueryDetector { } @Override - public void onError(int status) throws RemoteException { - Slog.v(TAG, "Initialization Error: (" + status + ")"); - // Do nothing - } - - @Override public void onHotwordDetectionServiceFailure( HotwordDetectionServiceFailure hotwordDetectionServiceFailure) throws RemoteException { @@ -420,6 +414,11 @@ public class VisualQueryDetector { } @Override + public void onSoundTriggerFailure(SoundTriggerFailure soundTriggerFailure) { + Slog.wtf(TAG, "Unexpected STFailure in VisualQueryDetector" + soundTriggerFailure); + } + + @Override public void onUnknownFailure(String errorMessage) throws RemoteException { Slog.v(TAG, "onUnknownFailure: " + errorMessage); Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> { diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 77bbeb59927a..74ab7095d392 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -112,6 +112,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; /** @@ -431,6 +432,7 @@ public abstract class WallpaperService extends Service { Message msg = mCaller.obtainMessageIO(MSG_WINDOW_RESIZED, reportDraw ? 1 : 0, mergedConfiguration); + mIWallpaperEngine.mPendingResizeCount.incrementAndGet(); mCaller.sendMessage(msg); } @@ -510,6 +512,7 @@ public abstract class WallpaperService extends Service { public Engine(Supplier<Long> clockFunction, Handler handler) { mClockFunction = clockFunction; mHandler = handler; + mMergedConfiguration.setOverrideConfiguration(getResources().getConfiguration()); } /** @@ -1051,6 +1054,10 @@ public abstract class WallpaperService extends Service { out.print(prefix); out.print("mZoom="); out.println(mZoom); out.print(prefix); out.print("mPreviewSurfacePosition="); out.println(mPreviewSurfacePosition); + final int pendingCount = mIWallpaperEngine.mPendingResizeCount.get(); + if (pendingCount != 0) { + out.print(prefix); out.print("mPendingResizeCount="); out.println(pendingCount); + } synchronized (mLock) { out.print(prefix); out.print("mPendingXOffset="); out.print(mPendingXOffset); out.print(" mPendingXOffset="); out.println(mPendingXOffset); @@ -1113,10 +1120,6 @@ public abstract class WallpaperService extends Service { } } - private void updateConfiguration(MergedConfiguration mergedConfiguration) { - mMergedConfiguration.setTo(mergedConfiguration); - } - void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) { if (mDestroyed) { Log.w(TAG, "Ignoring updateSurface due to destroyed"); @@ -1165,7 +1168,7 @@ public abstract class WallpaperService extends Service { | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; final Configuration config = mMergedConfiguration.getMergedConfiguration(); - final Rect maxBounds = config.windowConfiguration.getMaxBounds(); + final Rect maxBounds = new Rect(config.windowConfiguration.getMaxBounds()); if (myWidth == ViewGroup.LayoutParams.MATCH_PARENT && myHeight == ViewGroup.LayoutParams.MATCH_PARENT) { mLayout.width = myWidth; @@ -1221,6 +1224,17 @@ public abstract class WallpaperService extends Service { final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight, View.VISIBLE, 0, 0, 0, mWinFrames, mMergedConfiguration, mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle); + final Rect outMaxBounds = mMergedConfiguration.getMergedConfiguration() + .windowConfiguration.getMaxBounds(); + if (!outMaxBounds.equals(maxBounds)) { + Log.i(TAG, "Retry updateSurface because bounds changed from relayout: " + + maxBounds + " -> " + outMaxBounds); + mSurfaceHolder.mSurfaceLock.unlock(); + mDrawingAllowed = false; + mCaller.sendMessage(mCaller.obtainMessageI(MSG_WINDOW_RESIZED, + redrawNeeded ? 1 : 0)); + return; + } final int transformHint = SurfaceControl.rotationToBufferTransform( (mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4); @@ -2324,6 +2338,8 @@ public abstract class WallpaperService extends Service { final IBinder mWindowToken; final int mWindowType; final boolean mIsPreview; + final AtomicInteger mPendingResizeCount = new AtomicInteger(); + boolean mReportDraw; boolean mShownReported; int mReqWidth; int mReqHeight; @@ -2579,11 +2595,7 @@ public abstract class WallpaperService extends Service { mEngine.doCommand(cmd); } break; case MSG_WINDOW_RESIZED: { - final boolean reportDraw = message.arg1 != 0; - mEngine.updateConfiguration(((MergedConfiguration) message.obj)); - mEngine.updateSurface(true, false, reportDraw); - mEngine.doOffsetsChanged(true); - mEngine.scaleAndCropScreenshot(); + handleResized((MergedConfiguration) message.obj, message.arg1 != 0); } break; case MSG_WINDOW_MOVED: { // Do nothing. What does it mean for a Wallpaper to move? @@ -2631,6 +2643,40 @@ public abstract class WallpaperService extends Service { Log.w(TAG, "Unknown message type " + message.what); } } + + /** + * In general this performs relayout for IWindow#resized. If there are several pending + * (in the message queue) MSG_WINDOW_RESIZED from server side, only the last one will be + * handled (ignore intermediate states). Note that this procedure cannot be skipped if the + * configuration is not changed because this is also used to dispatch insets changes. + */ + private void handleResized(MergedConfiguration config, boolean reportDraw) { + // The config can be null when retrying for a changed config from relayout, otherwise + // it is from IWindow#resized which always sends non-null config. + final int pendingCount = config != null ? mPendingResizeCount.decrementAndGet() : -1; + if (reportDraw) { + mReportDraw = true; + } + if (pendingCount > 0) { + if (DEBUG) { + Log.d(TAG, "Skip outdated resize, bounds=" + + config.getMergedConfiguration().windowConfiguration.getMaxBounds() + + " pendingCount=" + pendingCount); + } + return; + } + if (config != null) { + if (DEBUG) { + Log.d(TAG, "Update config from resized, bounds=" + + config.getMergedConfiguration().windowConfiguration.getMaxBounds()); + } + mEngine.mMergedConfiguration.setTo(config); + } + mEngine.updateSurface(true /* forceRelayout */, false /* forceReport */, mReportDraw); + mReportDraw = false; + mEngine.doOffsetsChanged(true); + mEngine.scaleAndCropScreenshot(); + } } /** diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 9a93e1b9e1f9..d06b0ce1a2d8 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -354,6 +354,15 @@ public class TimeUtils { } /** @hide Just for debugging; not internationalized. */ + public static void formatDuration(long time, long now, StringBuilder sb) { + if (time == 0) { + sb.append("--"); + return; + } + formatDuration(time-now, sb, 0); + } + + /** @hide Just for debugging; not internationalized. */ public static void formatDuration(long time, long now, PrintWriter pw) { if (time == 0) { pw.print("--"); diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java index 5dd2d82200bc..edc59931d4f8 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -195,7 +195,7 @@ public final class Choreographer { private boolean mDebugPrintNextFrameTimeDelta; private int mFPSDivisor = 1; - private DisplayEventReceiver.VsyncEventData mLastVsyncEventData = + private final DisplayEventReceiver.VsyncEventData mLastVsyncEventData = new DisplayEventReceiver.VsyncEventData(); private final FrameData mFrameData = new FrameData(); @@ -860,7 +860,7 @@ public final class Choreographer { mFrameScheduled = false; mLastFrameTimeNanos = frameTimeNanos; mLastFrameIntervalNanos = frameIntervalNanos; - mLastVsyncEventData = vsyncEventData; + mLastVsyncEventData.copyFrom(vsyncEventData); } if (resynced && Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { @@ -1262,7 +1262,7 @@ public final class Choreographer { private boolean mHavePendingVsync; private long mTimestampNanos; private int mFrame; - private VsyncEventData mLastVsyncEventData = new VsyncEventData(); + private final VsyncEventData mLastVsyncEventData = new VsyncEventData(); FrameDisplayEventReceiver(Looper looper, int vsyncSource, long layerHandle) { super(looper, vsyncSource, /* eventRegistration */ 0, layerHandle); @@ -1302,7 +1302,7 @@ public final class Choreographer { mTimestampNanos = timestampNanos; mFrame = frame; - mLastVsyncEventData = vsyncEventData; + mLastVsyncEventData.copyFrom(vsyncEventData); Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); diff --git a/core/java/android/view/DisplayEventReceiver.java b/core/java/android/view/DisplayEventReceiver.java index 03074894b2ff..54db34e788e9 100644 --- a/core/java/android/view/DisplayEventReceiver.java +++ b/core/java/android/view/DisplayEventReceiver.java @@ -81,7 +81,10 @@ public abstract class DisplayEventReceiver { // GC'd while the native peer of the receiver is using them. private MessageQueue mMessageQueue; + private final VsyncEventData mVsyncEventData = new VsyncEventData(); + private static native long nativeInit(WeakReference<DisplayEventReceiver> receiver, + WeakReference<VsyncEventData> vsyncEventData, MessageQueue messageQueue, int vsyncSource, int eventRegistration, long layerHandle); private static native long nativeGetDisplayEventReceiverFinalizer(); @FastNative @@ -124,7 +127,9 @@ public abstract class DisplayEventReceiver { } mMessageQueue = looper.getQueue(); - mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), mMessageQueue, + mReceiverPtr = nativeInit(new WeakReference<DisplayEventReceiver>(this), + new WeakReference<VsyncEventData>(mVsyncEventData), + mMessageQueue, vsyncSource, eventRegistration, layerHandle); mFreeNativeResources = sNativeAllocationRegistry.registerNativeAllocation(this, mReceiverPtr); @@ -147,9 +152,6 @@ public abstract class DisplayEventReceiver { * @hide */ public static final class VsyncEventData { - static final FrameTimeline[] INVALID_FRAME_TIMELINES = - {new FrameTimeline(FrameInfo.INVALID_VSYNC_ID, Long.MAX_VALUE, Long.MAX_VALUE)}; - // The amount of frame timeline choices. // Must be in sync with VsyncEventData::kFrameTimelinesLength in // frameworks/native/libs/gui/include/gui/VsyncEventData.h. If they do not match, a runtime @@ -157,22 +159,32 @@ public abstract class DisplayEventReceiver { static final int FRAME_TIMELINES_LENGTH = 7; public static class FrameTimeline { + FrameTimeline() {} + + // Called from native code. + @SuppressWarnings("unused") FrameTimeline(long vsyncId, long expectedPresentationTime, long deadline) { this.vsyncId = vsyncId; this.expectedPresentationTime = expectedPresentationTime; this.deadline = deadline; } + void copyFrom(FrameTimeline other) { + vsyncId = other.vsyncId; + expectedPresentationTime = other.expectedPresentationTime; + deadline = other.deadline; + } + // The frame timeline vsync id, used to correlate a frame // produced by HWUI with the timeline data stored in Surface Flinger. - public final long vsyncId; + public long vsyncId = FrameInfo.INVALID_VSYNC_ID; // The frame timestamp for when the frame is expected to be presented. - public final long expectedPresentationTime; + public long expectedPresentationTime = Long.MAX_VALUE; // The frame deadline timestamp in {@link System#nanoTime()} timebase that it is // allotted for the frame to be completed. - public final long deadline; + public long deadline = Long.MAX_VALUE; } /** @@ -180,11 +192,18 @@ public abstract class DisplayEventReceiver { * {@link FrameInfo#VSYNC} to the current vsync in case Choreographer callback was heavily * delayed by the app. */ - public final long frameInterval; + public long frameInterval = -1; public final FrameTimeline[] frameTimelines; - public final int preferredFrameTimelineIndex; + public int preferredFrameTimelineIndex = 0; + + VsyncEventData() { + frameTimelines = new FrameTimeline[FRAME_TIMELINES_LENGTH]; + for (int i = 0; i < frameTimelines.length; i++) { + frameTimelines[i] = new FrameTimeline(); + } + } // Called from native code. @SuppressWarnings("unused") @@ -195,10 +214,12 @@ public abstract class DisplayEventReceiver { this.frameInterval = frameInterval; } - VsyncEventData() { - this.frameInterval = -1; - this.frameTimelines = INVALID_FRAME_TIMELINES; - this.preferredFrameTimelineIndex = 0; + void copyFrom(VsyncEventData other) { + preferredFrameTimelineIndex = other.preferredFrameTimelineIndex; + frameInterval = other.frameInterval; + for (int i = 0; i < frameTimelines.length; i++) { + frameTimelines[i].copyFrom(other.frameTimelines[i]); + } } public FrameTimeline preferredFrameTimeline() { @@ -304,9 +325,8 @@ public abstract class DisplayEventReceiver { // Called from native code. @SuppressWarnings("unused") - private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame, - VsyncEventData vsyncEventData) { - onVsync(timestampNanos, physicalDisplayId, frame, vsyncEventData); + private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) { + onVsync(timestampNanos, physicalDisplayId, frame, mVsyncEventData); } // Called from native code. diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index cd89a561074c..bd6224b07b16 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -120,6 +120,8 @@ public class SurfaceControlViewHost { private ISurfaceControlViewHost mRemoteInterface = new ISurfaceControlViewHostImpl(); + private ViewRootImpl.ConfigChangedCallback mConfigChangedCallback; + /** * Package encapsulating a Surface hierarchy which contains interactive view * elements. It's expected to get this object from @@ -303,10 +305,11 @@ public class SurfaceControlViewHost { /** @hide */ public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d, @NonNull WindowlessWindowManager wwm, @NonNull String callsite) { + mSurfaceControl = wwm.mRootSurface; mWm = wwm; mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout()); mCloseGuard.openWithCallSite("release", callsite); - addConfigCallback(c, d); + setConfigCallback(c, d); WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot); @@ -356,21 +359,23 @@ public class SurfaceControlViewHost { mViewRoot = new ViewRootImpl(context, display, mWm, new WindowlessWindowLayout()); mCloseGuard.openWithCallSite("release", callsite); - addConfigCallback(context, display); + setConfigCallback(context, display); WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot); mAccessibilityEmbeddedConnection = mViewRoot.getAccessibilityEmbeddedConnection(); } - private void addConfigCallback(Context c, Display d) { + private void setConfigCallback(Context c, Display d) { final IBinder token = c.getWindowContextToken(); - mViewRoot.addConfigCallback((conf) -> { + mConfigChangedCallback = conf -> { if (token instanceof WindowTokenClient) { final WindowTokenClient w = (WindowTokenClient) token; w.onConfigurationChanged(conf, d.getDisplayId(), true); } - }); + }; + + ViewRootImpl.addConfigCallback(mConfigChangedCallback); } /** @@ -385,8 +390,7 @@ public class SurfaceControlViewHost { mCloseGuard.warnIfOpen(); } // We aren't on the UI thread here so we need to pass false to doDie - mViewRoot.die(false /* immediate */); - WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot); + doRelease(false /* immediate */); } /** @@ -399,8 +403,7 @@ public class SurfaceControlViewHost { public @Nullable SurfacePackage getSurfacePackage() { if (mSurfaceControl != null && mAccessibilityEmbeddedConnection != null) { return new SurfacePackage(new SurfaceControl(mSurfaceControl, "getSurfacePackage"), - mAccessibilityEmbeddedConnection, - mWm.getFocusGrantToken(), mRemoteInterface); + mAccessibilityEmbeddedConnection, getFocusGrantToken(), mRemoteInterface); } else { return null; } @@ -496,7 +499,16 @@ public class SurfaceControlViewHost { */ public void release() { // ViewRoot will release mSurfaceControl for us. - mViewRoot.die(true /* immediate */); + doRelease(true /* immediate */); + } + + private void doRelease(boolean immediate) { + if (mConfigChangedCallback != null) { + ViewRootImpl.removeConfigCallback(mConfigChangedCallback); + mConfigChangedCallback = null; + } + + mViewRoot.die(immediate); WindowManagerGlobal.getInstance().removeWindowlessRoot(mViewRoot); mReleased = true; mCloseGuard.close(); @@ -506,7 +518,7 @@ public class SurfaceControlViewHost { * @hide */ public IBinder getFocusGrantToken() { - return mWm.getFocusGrantToken(); + return mWm.getFocusGrantToken(getWindowToken().asBinder()); } private void addWindowToken(WindowManager.LayoutParams attrs) { diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index cdea97ce9b7f..0e4cf89e7772 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1854,6 +1854,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall applyTransactionOnVriDraw(transaction); } mSurfacePackage = p; + + if (isFocused()) { + requestEmbeddedFocus(true); + } invalidate(); } @@ -1947,8 +1951,12 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall @Override protected void onFocusChanged(boolean gainFocus, @FocusDirection int direction, - @Nullable Rect previouslyFocusedRect) { + @Nullable Rect previouslyFocusedRect) { super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + requestEmbeddedFocus(gainFocus); + } + + private void requestEmbeddedFocus(boolean gainFocus) { final ViewRootImpl viewRoot = getViewRootImpl(); if (mSurfacePackage == null || viewRoot == null) { return; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 055b5cb70562..86e7fb09c5ea 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -11263,13 +11263,19 @@ public final class ViewRootImpl implements ViewParent, } if (syncBuffer) { - mBlastBufferQueue.syncNextTransaction(new Consumer<Transaction>() { - @Override - public void accept(Transaction transaction) { - surfaceSyncGroup.addTransaction(transaction); - surfaceSyncGroup.markSyncReady(); - } + boolean result = mBlastBufferQueue.syncNextTransaction(transaction -> { + surfaceSyncGroup.addTransaction(transaction); + surfaceSyncGroup.markSyncReady(); }); + if (!result) { + // syncNextTransaction can only return false if something is already trying + // to sync the same frame in the same BBQ. That shouldn't be possible, but + // if it did happen, invoke markSyncReady so the active SSG doesn't get + // stuck. + Log.e(mTag, "Unable to syncNextTransaction. Possibly something else is" + + " trying to sync?"); + surfaceSyncGroup.markSyncReady(); + } } return didProduceBuffer -> { @@ -11283,7 +11289,7 @@ public final class ViewRootImpl implements ViewParent, // the next draw attempt. The next transaction and transaction complete callback // were only set for the current draw attempt. if (!didProduceBuffer) { - mBlastBufferQueue.syncNextTransaction(null); + mBlastBufferQueue.clearSyncTransaction(); // Gather the transactions that were sent to mergeWithNextTransaction // since the frame didn't draw on this vsync. It's possible the frame will diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 98681446446b..96bfb2d9e1e6 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -106,8 +106,22 @@ public class WindowlessWindowManager implements IWindowSession { mConfiguration.setTo(configuration); } - IBinder getFocusGrantToken() { - return mFocusGrantToken; + IBinder getFocusGrantToken(IBinder window) { + synchronized (this) { + // This can only happen if someone requested the focusGrantToken before setView was + // called for the SCVH. In that case, use the root focusGrantToken since this will be + // the same token sent to WMS for the root window once setView is called. + if (mStateForWindow.isEmpty()) { + return mFocusGrantToken; + } + State state = mStateForWindow.get(window); + if (state != null) { + return state.mFocusGrantToken; + } + } + + Log.w(TAG, "Failed to get focusGrantToken. Returning null token"); + return null; } /** diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl index 1fac142df481..390503b80bad 100644 --- a/core/java/android/view/accessibility/IAccessibilityManager.aidl +++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl @@ -62,7 +62,7 @@ interface IAccessibilityManager { in IAccessibilityInteractionConnection connection); void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient client, - in AccessibilityServiceInfo info, int flags); + in AccessibilityServiceInfo info, int userId, int flags); void unregisterUiTestAutomationService(IAccessibilityServiceClient client); diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java index 7d1dc7660871..84ef22637cb0 100644 --- a/core/java/android/view/animation/AnimationUtils.java +++ b/core/java/android/view/animation/AnimationUtils.java @@ -28,6 +28,7 @@ import android.content.res.XmlResourceParser; import android.os.SystemClock; import android.util.AttributeSet; import android.util.Xml; +import android.view.InflateException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -137,16 +138,9 @@ public class AnimationUtils { try { parser = context.getResources().getAnimation(id); return createAnimationFromXml(context, parser); - } catch (XmlPullParserException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; - } catch (IOException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; + } catch (XmlPullParserException | IOException ex) { + throw new NotFoundException( + "Can't load animation resource ID #0x" + Integer.toHexString(id), ex); } finally { if (parser != null) parser.close(); } @@ -159,8 +153,9 @@ public class AnimationUtils { } @UnsupportedAppUsage - private static Animation createAnimationFromXml(Context c, XmlPullParser parser, - AnimationSet parent, AttributeSet attrs) throws XmlPullParserException, IOException { + private static Animation createAnimationFromXml( + Context c, XmlPullParser parser, AnimationSet parent, AttributeSet attrs) + throws XmlPullParserException, IOException, InflateException { Animation anim = null; @@ -168,8 +163,8 @@ public class AnimationUtils { int type; int depth = parser.getDepth(); - while (((type=parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) - && type != XmlPullParser.END_DOCUMENT) { + while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) + && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; @@ -193,7 +188,7 @@ public class AnimationUtils { } else if (name.equals("extend")) { anim = new ExtendAnimation(c, attrs); } else { - throw new RuntimeException("Unknown animation name: " + parser.getName()); + throw new InflateException("Unknown animation name: " + parser.getName()); } if (parent != null) { @@ -220,29 +215,24 @@ public class AnimationUtils { try { parser = context.getResources().getAnimation(id); return createLayoutAnimationFromXml(context, parser); - } catch (XmlPullParserException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; - } catch (IOException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; + } catch (XmlPullParserException | IOException | InflateException ex) { + throw new NotFoundException( + "Can't load animation resource ID #0x" + Integer.toHexString(id), ex); } finally { if (parser != null) parser.close(); } } - private static LayoutAnimationController createLayoutAnimationFromXml(Context c, - XmlPullParser parser) throws XmlPullParserException, IOException { + private static LayoutAnimationController createLayoutAnimationFromXml( + Context c, XmlPullParser parser) + throws XmlPullParserException, IOException, InflateException { return createLayoutAnimationFromXml(c, parser, Xml.asAttributeSet(parser)); } - private static LayoutAnimationController createLayoutAnimationFromXml(Context c, - XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { + private static LayoutAnimationController createLayoutAnimationFromXml( + Context c, XmlPullParser parser, AttributeSet attrs) + throws XmlPullParserException, IOException, InflateException { LayoutAnimationController controller = null; @@ -263,7 +253,7 @@ public class AnimationUtils { } else if ("gridLayoutAnimation".equals(name)) { controller = new GridLayoutAnimationController(c, attrs); } else { - throw new RuntimeException("Unknown layout animation name: " + name); + throw new InflateException("Unknown layout animation name: " + name); } } @@ -342,16 +332,9 @@ public class AnimationUtils { try { parser = context.getResources().getAnimation(id); return createInterpolatorFromXml(context.getResources(), context.getTheme(), parser); - } catch (XmlPullParserException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; - } catch (IOException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; + } catch (XmlPullParserException | IOException | InflateException ex) { + throw new NotFoundException( + "Can't load animation resource ID #0x" + Integer.toHexString(id), ex); } finally { if (parser != null) parser.close(); } @@ -367,30 +350,26 @@ public class AnimationUtils { * @throws NotFoundException * @hide */ - public static Interpolator loadInterpolator(Resources res, Theme theme, int id) throws NotFoundException { + public static Interpolator loadInterpolator(Resources res, Theme theme, int id) + throws NotFoundException { XmlResourceParser parser = null; try { parser = res.getAnimation(id); return createInterpolatorFromXml(res, theme, parser); - } catch (XmlPullParserException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; - } catch (IOException ex) { - NotFoundException rnf = new NotFoundException("Can't load animation resource ID #0x" + - Integer.toHexString(id)); - rnf.initCause(ex); - throw rnf; + } catch (XmlPullParserException | IOException | InflateException ex) { + throw new NotFoundException( + "Can't load animation resource ID #0x" + Integer.toHexString(id), ex); } finally { - if (parser != null) + if (parser != null) { parser.close(); + } } } - private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser) - throws XmlPullParserException, IOException { + private static Interpolator createInterpolatorFromXml( + Resources res, Theme theme, XmlPullParser parser) + throws XmlPullParserException, IOException, InflateException { BaseInterpolator interpolator = null; @@ -430,7 +409,7 @@ public class AnimationUtils { } else if (name.equals("pathInterpolator")) { interpolator = new PathInterpolator(res, theme, attrs); } else { - throw new RuntimeException("Unknown interpolator name: " + parser.getName()); + throw new InflateException("Unknown interpolator name: " + parser.getName()); } } return interpolator; diff --git a/core/java/android/view/translation/Translator.java b/core/java/android/view/translation/Translator.java index 70db6e513c88..50249da08c3c 100644 --- a/core/java/android/view/translation/Translator.java +++ b/core/java/android/view/translation/Translator.java @@ -18,7 +18,6 @@ package android.view.translation; import static android.view.translation.TranslationManager.STATUS_SYNC_CALL_FAIL; import static android.view.translation.TranslationManager.SYNC_CALLS_TIMEOUT_MS; -import static android.view.translation.UiTranslationController.DEBUG; import android.annotation.CallbackExecutor; import android.annotation.NonNull; @@ -402,7 +401,7 @@ public class Translator { @Override public void onTranslationResponse(TranslationResponse response) throws RemoteException { - if (DEBUG) { + if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) { Log.i(TAG, "onTranslationResponse called."); } final Runnable runnable = diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index 514df59f1989..140e3f1b6fdc 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -122,8 +122,9 @@ public class UiTranslationController implements Dumpable { Log.i(TAG, "Cannot update " + stateToString(state) + " for destroyed " + mActivity); return; } + boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG); Log.i(TAG, "updateUiTranslationState state: " + stateToString(state) - + (DEBUG ? (", views: " + views + ", spec: " + uiTranslationSpec) : "")); + + (isLoggable ? (", views: " + views + ", spec: " + uiTranslationSpec) : "")); synchronized (mLock) { mCurrentState = state; if (views != null) { @@ -237,7 +238,7 @@ public class UiTranslationController implements Dumpable { } pw.print(outerPrefix); pw.print("padded views: "); pw.println(mViewsToPadContent); } - if (DEBUG) { + if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) { dumpViewByTraversal(outerPrefix, pw); } } @@ -345,6 +346,7 @@ public class UiTranslationController implements Dumpable { */ private void onVirtualViewTranslationCompleted( SparseArray<LongSparseArray<ViewTranslationResponse>> translatedResult) { + boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG); if (mActivity.isDestroyed()) { Log.v(TAG, "onTranslationCompleted:" + mActivity + "is destroyed."); return; @@ -369,7 +371,7 @@ public class UiTranslationController implements Dumpable { } final LongSparseArray<ViewTranslationResponse> virtualChildResponse = translatedResult.valueAt(i); - if (DEBUG) { + if (isLoggable) { Log.v(TAG, "onVirtualViewTranslationCompleted: received response for " + "AutofillId " + autofillId); } @@ -379,7 +381,7 @@ public class UiTranslationController implements Dumpable { } mActivity.runOnUiThread(() -> { if (view.getViewTranslationCallback() == null) { - if (DEBUG) { + if (isLoggable) { Log.d(TAG, view + " doesn't support showing translation because of " + "null ViewTranslationCallback."); } @@ -397,12 +399,13 @@ public class UiTranslationController implements Dumpable { * The method is used to handle the translation result for non-vertual views. */ private void onTranslationCompleted(SparseArray<ViewTranslationResponse> translatedResult) { + boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG); if (mActivity.isDestroyed()) { Log.v(TAG, "onTranslationCompleted:" + mActivity + "is destroyed."); return; } final int resultCount = translatedResult.size(); - if (DEBUG) { + if (isLoggable) { Log.v(TAG, "onTranslationCompleted: receive " + resultCount + " responses."); } synchronized (mLock) { @@ -413,7 +416,7 @@ public class UiTranslationController implements Dumpable { } for (int i = 0; i < resultCount; i++) { final ViewTranslationResponse response = translatedResult.valueAt(i); - if (DEBUG) { + if (isLoggable) { Log.v(TAG, "onTranslationCompleted: " + sanitizedViewTranslationResponse(response)); } @@ -443,7 +446,7 @@ public class UiTranslationController implements Dumpable { (TextViewTranslationCallback) callback; if (textViewCallback.isShowingTranslation() || textViewCallback.isAnimationRunning()) { - if (DEBUG) { + if (isLoggable) { Log.d(TAG, "Duplicate ViewTranslationResponse for " + autofillId + ". Ignoring."); } @@ -458,7 +461,7 @@ public class UiTranslationController implements Dumpable { callback = new TextViewTranslationCallback(); view.setViewTranslationCallback(callback); } else { - if (DEBUG) { + if (isLoggable) { Log.d(TAG, view + " doesn't support showing translation because of " + "null ViewTranslationCallback."); } @@ -506,7 +509,7 @@ public class UiTranslationController implements Dumpable { final TranslationRequest request = new TranslationRequest.Builder() .setViewTranslationRequests(requests) .build(); - if (DEBUG) { + if (Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG)) { StringBuilder msg = new StringBuilder("sendTranslationRequest:{requests=["); for (ViewTranslationRequest viewRequest: requests) { msg.append("{request=") @@ -636,6 +639,7 @@ public class UiTranslationController implements Dumpable { private void runForEachView(BiConsumer<View, ViewTranslationCallback> action) { synchronized (mLock) { + boolean isLoggable = Log.isLoggable(UiTranslationManager.LOG_TAG, Log.DEBUG); final ArrayMap<AutofillId, WeakReference<View>> views = new ArrayMap<>(mViews); if (views.size() == 0) { Log.w(TAG, "No views can be excuted for runForEachView."); @@ -644,12 +648,12 @@ public class UiTranslationController implements Dumpable { final int viewCounts = views.size(); for (int i = 0; i < viewCounts; i++) { final View view = views.valueAt(i).get(); - if (DEBUG) { + if (isLoggable) { Log.d(TAG, "runForEachView for autofillId = " + (view != null ? view.getAutofillId() : " null")); } if (view == null || view.getViewTranslationCallback() == null) { - if (DEBUG) { + if (isLoggable) { Log.d(TAG, "View was gone or ViewTranslationCallback for autofillId " + "= " + views.keyAt(i)); } diff --git a/core/java/android/window/SurfaceSyncGroup.java b/core/java/android/window/SurfaceSyncGroup.java index 7f99fb7e7815..b3d512488239 100644 --- a/core/java/android/window/SurfaceSyncGroup.java +++ b/core/java/android/window/SurfaceSyncGroup.java @@ -120,6 +120,7 @@ public final class SurfaceSyncGroup { private static HandlerThread sHandlerThread; private Handler mHandler; + @GuardedBy("mLock") private boolean mTimeoutAdded; private static boolean isLocalBinder(IBinder binder) { @@ -234,6 +235,9 @@ public final class SurfaceSyncGroup { * SurfaceSyncGroup have completed their sync. */ public void markSyncReady() { + if (DEBUG) { + Log.d(TAG, "markSyncReady " + mName); + } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "markSyncReady " + mName); } @@ -456,7 +460,15 @@ public final class SurfaceSyncGroup { */ public void addTransaction(@NonNull Transaction transaction) { synchronized (mLock) { - mTransaction.merge(transaction); + // If the caller tries to add a transaction to a completed SSG, just apply the + // transaction immediately since there's nothing to wait on. + if (mFinished) { + Log.w(TAG, "Adding transaction to a completed SurfaceSyncGroup(" + mName + "). " + + " Applying immediately"); + transaction.apply(); + } else { + mTransaction.merge(transaction); + } } } @@ -509,7 +521,7 @@ public final class SurfaceSyncGroup { private boolean addLocalSync(ISurfaceSyncGroup childSyncToken, boolean parentSyncGroupMerge) { if (DEBUG) { - Log.d(TAG, "Adding local sync " + mName); + Log.d(TAG, "Adding local sync to " + mName); } SurfaceSyncGroup childSurfaceSyncGroup = getSurfaceSyncGroup(childSyncToken); @@ -540,7 +552,7 @@ public final class SurfaceSyncGroup { private void setTransactionCallbackFromParent(ISurfaceSyncGroup parentSyncGroup, ITransactionReadyCallback transactionReadyCallback) { if (DEBUG) { - Log.d(TAG, "setTransactionCallbackFromParent " + mName); + Log.d(TAG, "setTransactionCallbackFromParent for child " + mName); } if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { @@ -677,7 +689,7 @@ public final class SurfaceSyncGroup { */ public ITransactionReadyCallback createTransactionReadyCallback(boolean parentSyncGroupMerge) { if (DEBUG) { - Log.d(TAG, "createTransactionReadyCallback " + mName); + Log.d(TAG, "createTransactionReadyCallback as part of " + mName); } ITransactionReadyCallback transactionReadyCallback = new ITransactionReadyCallback.Stub() { @@ -780,7 +792,7 @@ public final class SurfaceSyncGroup { Runnable runnable = () -> { Log.e(TAG, "Failed to receive transaction ready in " + TRANSACTION_READY_TIMEOUT - + "ms. Marking SurfaceSyncGroup as ready " + mName); + + "ms. Marking SurfaceSyncGroup(" + mName + ") as ready"); // Clear out any pending syncs in case the other syncs can't complete or timeout due to // a crash. synchronized (mLock) { diff --git a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl index ad0d1a401991..380118846dc7 100644 --- a/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl +++ b/core/java/com/android/internal/app/IHotwordRecognitionStatusCallback.aidl @@ -20,6 +20,7 @@ import android.hardware.soundtrigger.SoundTrigger; import android.service.voice.HotwordDetectedResult; import android.service.voice.HotwordDetectionServiceFailure; import android.service.voice.HotwordRejectedResult; +import android.service.voice.SoundTriggerFailure; import android.service.voice.VisualQueryDetectionServiceFailure; /** @@ -57,13 +58,6 @@ oneway interface IHotwordRecognitionStatusCallback { void onRejected(in HotwordRejectedResult result); /** - * Called when the detection fails due to an error. - * - * @param status The error code that was seen. - */ - void onError(int status); - - /** * Called when the detection fails due to an error occurs in the * {@link HotwordDetectionService}. * @@ -84,6 +78,15 @@ oneway interface IHotwordRecognitionStatusCallback { in VisualQueryDetectionServiceFailure visualQueryDetectionServiceFailure); /** + * Called when the detection fails due to an error occurs in the + * {@link com.android.server.soundtrigger.SoundTriggerService}. + * + * @param soundTriggerFailure It provides the error code, error message and + * suggested action. + */ + void onSoundTriggerFailure(in SoundTriggerFailure soundTriggerFailure); + + /** * Called when the detection fails due to an unknown error occurs. * * @param errorMessage It provides the error message. diff --git a/core/java/com/android/internal/app/OWNERS b/core/java/com/android/internal/app/OWNERS index a1d571fc4350..52f18fb80c27 100644 --- a/core/java/com/android/internal/app/OWNERS +++ b/core/java/com/android/internal/app/OWNERS @@ -1,15 +1,16 @@ per-file *AppOp* = file:/core/java/android/permission/OWNERS per-file UnlaunchableAppActivity.java = file:/core/java/android/app/admin/WorkProfile_OWNERS per-file IntentForwarderActivity.java = file:/core/java/android/app/admin/WorkProfile_OWNERS -per-file *Resolver* = file:/packages/SystemUI/OWNERS -per-file *Chooser* = file:/packages/SystemUI/OWNERS -per-file SimpleIconFactory.java = file:/packages/SystemUI/OWNERS -per-file AbstractMultiProfilePagerAdapter.java = file:/packages/SystemUI/OWNERS -per-file *EmptyStateProvider.java = file:/packages/SystemUI/OWNERS per-file NetInitiatedActivity.java = file:/location/java/android/location/OWNERS per-file *BatteryStats* = file:/BATTERY_STATS_OWNERS per-file *SoundTrigger* = file:/media/java/android/media/soundtrigger/OWNERS +# Chooser and Resolver. +per-file *Chooser* = file:chooser/OWNERS +per-file *Resolver* = file:chooser/OWNERS +per-file SimpleIconFactory.java = file:chooser/OWNERS +per-file AbstractMultiProfilePagerAdapter.java = file:chooser/OWNERS +per-file *EmptyStateProvider.java = file:chooser/OWNERS # Voice Interaction per-file *Assist* = file:/core/java/android/service/voice/OWNERS diff --git a/core/java/com/android/internal/app/chooser/OWNERS b/core/java/com/android/internal/app/chooser/OWNERS index a6f1632e7b8c..0844cfa6d053 100644 --- a/core/java/com/android/internal/app/chooser/OWNERS +++ b/core/java/com/android/internal/app/chooser/OWNERS @@ -1 +1,3 @@ -file:/packages/SystemUI/OWNERS
\ No newline at end of file +# Bug component: 324112 + +include platform/packages/modules/IntentResolver:/OWNERS diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index cee8c1a47769..3633d9101fb9 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -1018,7 +1018,7 @@ public final class Zygote { * Applies debugger system properties to the zygote arguments. * * For eng builds all apps are debuggable. On userdebug and user builds - * if persist.debuggable.dalvik.vm.jdwp.enabled is 1 all apps are + * if persist.debug.dalvik.vm.jdwp.enabled is 1 all apps are * debuggable. Otherwise, the debugger state is specified via the * "--enable-jdwp" flag in the spawn request. * diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 1172f7ba447a..aa2318d219f8 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -50,6 +50,7 @@ import android.media.Image; import android.media.ImageReader; import android.os.SystemProperties; import android.util.Slog; +import android.view.InflateException; import android.view.SurfaceControl; import android.view.WindowManager.LayoutParams; import android.view.WindowManager.TransitionOldType; @@ -1264,7 +1265,7 @@ public class TransitionAnimation { public static Animation loadAnimationSafely(Context context, int resId, String tag) { try { return AnimationUtils.loadAnimation(context, resId); - } catch (Resources.NotFoundException e) { + } catch (Resources.NotFoundException | InflateException e) { Slog.w(tag, "Unable to load animation resource", e); return null; } diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index 03815108f6dd..55aa7117221e 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -125,26 +125,24 @@ private: jobject mObject; }; -static void nativeSyncNextTransaction(JNIEnv* env, jclass clazz, jlong ptr, jobject callback, +static bool nativeSyncNextTransaction(JNIEnv* env, jclass clazz, jlong ptr, jobject callback, jboolean acquireSingleBuffer) { + LOG_ALWAYS_FATAL_IF(!callback, "callback passed in to syncNextTransaction must not be NULL"); + sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); JavaVM* vm = nullptr; LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&vm) != JNI_OK, "Unable to get Java VM"); - if (!callback) { - queue->syncNextTransaction(nullptr, acquireSingleBuffer); - } else { - auto globalCallbackRef = - std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback)); - queue->syncNextTransaction( - [globalCallbackRef](SurfaceComposerClient::Transaction* t) { - JNIEnv* env = getenv(globalCallbackRef->vm()); - env->CallVoidMethod(globalCallbackRef->object(), gTransactionConsumer.accept, - env->NewObject(gTransactionClassInfo.clazz, - gTransactionClassInfo.ctor, - reinterpret_cast<jlong>(t))); - }, - acquireSingleBuffer); - } + + auto globalCallbackRef = std::make_shared<JGlobalRefHolder>(vm, env->NewGlobalRef(callback)); + return queue->syncNextTransaction( + [globalCallbackRef](SurfaceComposerClient::Transaction* t) { + JNIEnv* env = getenv(globalCallbackRef->vm()); + env->CallVoidMethod(globalCallbackRef->object(), gTransactionConsumer.accept, + env->NewObject(gTransactionClassInfo.clazz, + gTransactionClassInfo.ctor, + reinterpret_cast<jlong>(t))); + }, + acquireSingleBuffer); } static void nativeStopContinuousSyncTransaction(JNIEnv* env, jclass clazz, jlong ptr) { @@ -152,6 +150,11 @@ static void nativeStopContinuousSyncTransaction(JNIEnv* env, jclass clazz, jlong queue->stopContinuousSyncTransaction(); } +static void nativeClearSyncTransaction(JNIEnv* env, jclass clazz, jlong ptr) { + sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); + queue->clearSyncTransaction(); +} + static void nativeUpdate(JNIEnv* env, jclass clazz, jlong ptr, jlong surfaceControl, jlong width, jlong height, jint format) { sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); @@ -207,8 +210,9 @@ static const JNINativeMethod gMethods[] = { {"nativeCreate", "(Ljava/lang/String;Z)J", (void*)nativeCreate}, {"nativeGetSurface", "(JZ)Landroid/view/Surface;", (void*)nativeGetSurface}, {"nativeDestroy", "(J)V", (void*)nativeDestroy}, - {"nativeSyncNextTransaction", "(JLjava/util/function/Consumer;Z)V", (void*)nativeSyncNextTransaction}, + {"nativeSyncNextTransaction", "(JLjava/util/function/Consumer;Z)Z", (void*)nativeSyncNextTransaction}, {"nativeStopContinuousSyncTransaction", "(J)V", (void*)nativeStopContinuousSyncTransaction}, + {"nativeClearSyncTransaction", "(J)V", (void*)nativeClearSyncTransaction}, {"nativeUpdate", "(JJJJI)V", (void*)nativeUpdate}, {"nativeMergeWithNextTransaction", "(JJJ)V", (void*)nativeMergeWithNextTransaction}, {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum}, diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp index dd72689206ba..410b44161cf6 100644 --- a/core/jni/android_view_DisplayEventReceiver.cpp +++ b/core/jni/android_view_DisplayEventReceiver.cpp @@ -48,12 +48,22 @@ static struct { struct { jclass clazz; + jmethodID init; + + jfieldID vsyncId; + jfieldID expectedPresentationTime; + jfieldID deadline; } frameTimelineClassInfo; struct { jclass clazz; + jmethodID init; + + jfieldID frameInterval; + jfieldID preferredFrameTimelineIndex; + jfieldID frameTimelines; } vsyncEventDataClassInfo; } gDisplayEventReceiverClassInfo; @@ -61,7 +71,7 @@ static struct { class NativeDisplayEventReceiver : public DisplayEventDispatcher { public: - NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, + NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, jobject vsyncEventDataWeak, const sp<MessageQueue>& messageQueue, jint vsyncSource, jint eventRegistration, jlong layerHandle); @@ -72,6 +82,7 @@ protected: private: jobject mReceiverWeakGlobal; + jobject mVsyncEventDataWeakGlobal; sp<MessageQueue> mMessageQueue; void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count, @@ -85,6 +96,7 @@ private: }; NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject receiverWeak, + jobject vsyncEventDataWeak, const sp<MessageQueue>& messageQueue, jint vsyncSource, jint eventRegistration, jlong layerHandle) @@ -96,6 +108,7 @@ NativeDisplayEventReceiver::NativeDisplayEventReceiver(JNIEnv* env, jobject rece reinterpret_cast<IBinder*>(layerHandle)) : nullptr), mReceiverWeakGlobal(env->NewGlobalRef(receiverWeak)), + mVsyncEventDataWeakGlobal(env->NewGlobalRef(vsyncEventDataWeak)), mMessageQueue(messageQueue) { ALOGV("receiver %p ~ Initializing display event receiver.", this); } @@ -154,12 +167,43 @@ void NativeDisplayEventReceiver::dispatchVsync(nsecs_t timestamp, PhysicalDispla JNIEnv* env = AndroidRuntime::getJNIEnv(); ScopedLocalRef<jobject> receiverObj(env, GetReferent(env, mReceiverWeakGlobal)); - if (receiverObj.get()) { + ScopedLocalRef<jobject> vsyncEventDataObj(env, GetReferent(env, mVsyncEventDataWeakGlobal)); + if (receiverObj.get() && vsyncEventDataObj.get()) { ALOGV("receiver %p ~ Invoking vsync handler.", this); - jobject javaVsyncEventData = createJavaVsyncEventData(env, vsyncEventData); + env->SetIntField(vsyncEventDataObj.get(), + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo + .preferredFrameTimelineIndex, + vsyncEventData.preferredFrameTimelineIndex); + env->SetLongField(vsyncEventDataObj.get(), + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval, + vsyncEventData.frameInterval); + + ScopedLocalRef<jobjectArray> + frameTimelinesObj(env, + reinterpret_cast<jobjectArray>( + env->GetObjectField(vsyncEventDataObj.get(), + gDisplayEventReceiverClassInfo + .vsyncEventDataClassInfo + .frameTimelines))); + for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) { + VsyncEventData::FrameTimeline& frameTimeline = vsyncEventData.frameTimelines[i]; + ScopedLocalRef<jobject> + frameTimelineObj(env, env->GetObjectArrayElement(frameTimelinesObj.get(), i)); + env->SetLongField(frameTimelineObj.get(), + gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId, + frameTimeline.vsyncId); + env->SetLongField(frameTimelineObj.get(), + gDisplayEventReceiverClassInfo.frameTimelineClassInfo + .expectedPresentationTime, + frameTimeline.expectedPresentationTime); + env->SetLongField(frameTimelineObj.get(), + gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline, + frameTimeline.deadlineTimestamp); + } + env->CallVoidMethod(receiverObj.get(), gDisplayEventReceiverClassInfo.dispatchVsync, - timestamp, displayId.value, count, javaVsyncEventData); + timestamp, displayId.value, count); ALOGV("receiver %p ~ Returned from vsync handler.", this); } @@ -227,8 +271,9 @@ void NativeDisplayEventReceiver::dispatchFrameRateOverrides( mMessageQueue->raiseAndClearException(env, "dispatchModeChanged"); } -static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject messageQueueObj, - jint vsyncSource, jint eventRegistration, jlong layerHandle) { +static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject vsyncEventDataWeak, + jobject messageQueueObj, jint vsyncSource, jint eventRegistration, + jlong layerHandle) { sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj); if (messageQueue == NULL) { jniThrowRuntimeException(env, "MessageQueue is not initialized."); @@ -236,8 +281,8 @@ static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject } sp<NativeDisplayEventReceiver> receiver = - new NativeDisplayEventReceiver(env, receiverWeak, messageQueue, vsyncSource, - eventRegistration, layerHandle); + new NativeDisplayEventReceiver(env, receiverWeak, vsyncEventDataWeak, messageQueue, + vsyncSource, eventRegistration, layerHandle); status_t status = receiver->initialize(); if (status) { String8 message; @@ -284,7 +329,9 @@ static jobject nativeGetLatestVsyncEventData(JNIEnv* env, jclass clazz, jlong re static const JNINativeMethod gMethods[] = { /* name, signature, funcPtr */ - {"nativeInit", "(Ljava/lang/ref/WeakReference;Landroid/os/MessageQueue;IIJ)J", + {"nativeInit", + "(Ljava/lang/ref/WeakReference;Ljava/lang/ref/WeakReference;Landroid/os/" + "MessageQueue;IIJ)J", (void*)nativeInit}, {"nativeGetDisplayEventReceiverFinalizer", "()J", (void*)nativeGetDisplayEventReceiverFinalizer}, @@ -301,8 +348,7 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { gDisplayEventReceiverClassInfo.clazz = MakeGlobalRefOrDie(env, clazz); gDisplayEventReceiverClassInfo.dispatchVsync = - GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", - "(JJILandroid/view/DisplayEventReceiver$VsyncEventData;)V"); + GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchVsync", "(JJI)V"); gDisplayEventReceiverClassInfo.dispatchHotplug = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.clazz, "dispatchHotplug", "(JJZ)V"); gDisplayEventReceiverClassInfo.dispatchModeChanged = @@ -328,6 +374,15 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { gDisplayEventReceiverClassInfo.frameTimelineClassInfo.init = GetMethodIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, "<init>", "(JJJ)V"); + gDisplayEventReceiverClassInfo.frameTimelineClassInfo.vsyncId = + GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, + "vsyncId", "J"); + gDisplayEventReceiverClassInfo.frameTimelineClassInfo.expectedPresentationTime = + GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, + "expectedPresentationTime", "J"); + gDisplayEventReceiverClassInfo.frameTimelineClassInfo.deadline = + GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.frameTimelineClassInfo.clazz, + "deadline", "J"); jclass vsyncEventDataClazz = FindClassOrDie(env, "android/view/DisplayEventReceiver$VsyncEventData"); @@ -339,6 +394,17 @@ int register_android_view_DisplayEventReceiver(JNIEnv* env) { "([Landroid/view/" "DisplayEventReceiver$VsyncEventData$FrameTimeline;IJ)V"); + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.preferredFrameTimelineIndex = + GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, + "preferredFrameTimelineIndex", "I"); + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameInterval = + GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, + "frameInterval", "J"); + gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.frameTimelines = + GetFieldIDOrDie(env, gDisplayEventReceiverClassInfo.vsyncEventDataClassInfo.clazz, + "frameTimelines", + "[Landroid/view/DisplayEventReceiver$VsyncEventData$FrameTimeline;"); + return res; } diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 9f10ae6066f5..5544701d9325 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -534,8 +534,10 @@ <!-- If this is true, long press on power button will be available from the non-interactive state --> <bool name="config_supportLongPressPowerWhenNonInteractive">false</bool> - <!-- If this is true, then keep dreaming when undocking. --> - <bool name="config_keepDreamingWhenUndocking">false</bool> + <!-- If this is true, then keep dreaming when unplugging. + This config was formerly known as config_keepDreamingWhenUndocking. + It has been updated to affect other plug types. --> + <bool name="config_keepDreamingWhenUnplugging">false</bool> <!-- The timeout (in ms) to wait before attempting to reconnect to the dream overlay service if it becomes disconnected --> @@ -4304,8 +4306,12 @@ This package must be trusted, as it has the permissions to control other applications on the device. Example: "com.android.wellbeing" + + Note: This config is deprecated, please use config_systemWellbeing instead. --> - <string name="config_defaultWellbeingPackage" translatable="false"></string> + <string name="config_defaultWellbeingPackage" translatable="false"> + @string/config_systemWellbeing + </string> <!-- The component name for the default system attention service. This service must be trusted, as it can be activated without explicit consent of the user. diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index a823d1fd8ff4..b93a78695207 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -1944,7 +1944,7 @@ <java-symbol type="bool" name="config_allowTheaterModeWakeFromLidSwitch" /> <java-symbol type="bool" name="config_allowTheaterModeWakeFromDock" /> <java-symbol type="bool" name="config_allowTheaterModeWakeFromWindowLayout" /> - <java-symbol type="bool" name="config_keepDreamingWhenUndocking" /> + <java-symbol type="bool" name="config_keepDreamingWhenUnplugging" /> <java-symbol type="integer" name="config_keyguardDrawnTimeout" /> <java-symbol type="bool" name="config_goToSleepOnButtonPressTheaterMode" /> <java-symbol type="bool" name="config_supportLongPressPowerWhenNonInteractive" /> @@ -3141,7 +3141,6 @@ <java-symbol type="style" name="AlertDialogWithEmergencyButton"/> <java-symbol type="string" name="work_mode_emergency_call_button" /> <java-symbol type="string" name="work_mode_off_title" /> - <java-symbol type="string" name="work_mode_off_message" /> <java-symbol type="string" name="work_mode_turn_on" /> <java-symbol type="string" name="deprecated_target_sdk_message" /> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index e59b2597722f..706845360bf4 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -1665,47 +1665,47 @@ easier. <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item> <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item> - <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item> + <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item> <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item> - <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item> - <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item> - <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item> - <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item> + <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item> + <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item> + <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item> + <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item> <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item> - <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item> + <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item> <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item> - <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item> + <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item> <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item> <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item> <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item> - <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item> - <item name="materialColorErrorContainer">@color/system_error_container_dark</item> + <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item> + <item name="materialColorErrorContainer">@color/system_error_container_light</item> <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item> - <item name="materialColorPrimaryInverse">@color/system_primary_light</item> + <item name="materialColorPrimaryInverse">@color/system_primary_dark</item> <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item> - <item name="materialColorSurfaceInverse">@color/system_surface_light</item> - <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item> - <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item> + <item name="materialColorSurfaceInverse">@color/system_surface_dark</item> + <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item> + <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item> <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item> - <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item> - <item name="materialColorOnBackground">@color/system_on_background_dark</item> + <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item> + <item name="materialColorOnBackground">@color/system_on_background_light</item> <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item> - <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item> - <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item> - <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item> - <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item> - <item name="materialColorSecondary">@color/system_secondary_dark</item> - <item name="materialColorOnError">@color/system_on_error_dark</item> - <item name="materialColorSurface">@color/system_surface_dark</item> - <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item> - <item name="materialColorTertiary">@color/system_tertiary_dark</item> - <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item> - <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item> - <item name="materialColorOutline">@color/system_outline_dark</item> - <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item> - <item name="materialColorOnPrimary">@color/system_on_primary_dark</item> - <item name="materialColorOnSurface">@color/system_on_surface_dark</item> - <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item> + <item name="materialColorOnSecondary">@color/system_on_secondary_light</item> + <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item> + <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item> + <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item> + <item name="materialColorSecondary">@color/system_secondary_light</item> + <item name="materialColorOnError">@color/system_on_error_light</item> + <item name="materialColorSurface">@color/system_surface_light</item> + <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item> + <item name="materialColorTertiary">@color/system_tertiary_light</item> + <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item> + <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item> + <item name="materialColorOutline">@color/system_outline_light</item> + <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item> + <item name="materialColorOnPrimary">@color/system_on_primary_light</item> + <item name="materialColorOnSurface">@color/system_on_surface_light</item> + <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item> </style> <!-- DeviceDefault style for input methods, which is used by the @@ -1758,47 +1758,47 @@ easier. <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item> <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item> - <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item> + <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item> <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item> - <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item> - <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item> - <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item> - <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item> + <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item> + <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item> + <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item> + <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item> <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item> - <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item> + <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item> <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item> - <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item> + <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item> <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item> <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item> <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item> - <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item> - <item name="materialColorErrorContainer">@color/system_error_container_dark</item> + <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item> + <item name="materialColorErrorContainer">@color/system_error_container_light</item> <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item> - <item name="materialColorPrimaryInverse">@color/system_primary_light</item> + <item name="materialColorPrimaryInverse">@color/system_primary_dark</item> <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item> - <item name="materialColorSurfaceInverse">@color/system_surface_light</item> - <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item> - <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item> + <item name="materialColorSurfaceInverse">@color/system_surface_dark</item> + <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item> + <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item> <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item> - <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item> - <item name="materialColorOnBackground">@color/system_on_background_dark</item> + <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item> + <item name="materialColorOnBackground">@color/system_on_background_light</item> <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item> - <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item> - <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item> - <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item> - <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item> - <item name="materialColorSecondary">@color/system_secondary_dark</item> - <item name="materialColorOnError">@color/system_on_error_dark</item> - <item name="materialColorSurface">@color/system_surface_dark</item> - <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item> - <item name="materialColorTertiary">@color/system_tertiary_dark</item> - <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item> - <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item> - <item name="materialColorOutline">@color/system_outline_dark</item> - <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item> - <item name="materialColorOnPrimary">@color/system_on_primary_dark</item> - <item name="materialColorOnSurface">@color/system_on_surface_dark</item> - <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item> + <item name="materialColorOnSecondary">@color/system_on_secondary_light</item> + <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item> + <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item> + <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item> + <item name="materialColorSecondary">@color/system_secondary_light</item> + <item name="materialColorOnError">@color/system_on_error_light</item> + <item name="materialColorSurface">@color/system_surface_light</item> + <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item> + <item name="materialColorTertiary">@color/system_tertiary_light</item> + <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item> + <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item> + <item name="materialColorOutline">@color/system_outline_light</item> + <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item> + <item name="materialColorOnPrimary">@color/system_on_primary_light</item> + <item name="materialColorOnSurface">@color/system_on_surface_light</item> + <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item> </style> <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert"> @@ -4043,16 +4043,16 @@ easier. <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item> <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item> <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item> - <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item> + <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item> <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item> <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item> <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item> <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item> <item name="materialColorErrorContainer">@color/system_error_container_light</item> <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item> - <item name="materialColorPrimaryInverse">@color/system_primary_light</item> + <item name="materialColorPrimaryInverse">@color/system_primary_dark</item> <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item> - <item name="materialColorSurfaceInverse">@color/system_surface_light</item> + <item name="materialColorSurfaceInverse">@color/system_surface_dark</item> <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item> <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item> <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item> @@ -4123,16 +4123,16 @@ easier. <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item> <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item> <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item> - <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item> + <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item> <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item> <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item> <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item> <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item> <item name="materialColorErrorContainer">@color/system_error_container_light</item> <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item> - <item name="materialColorPrimaryInverse">@color/system_primary_light</item> + <item name="materialColorPrimaryInverse">@color/system_primary_dark</item> <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item> - <item name="materialColorSurfaceInverse">@color/system_surface_light</item> + <item name="materialColorSurfaceInverse">@color/system_surface_dark</item> <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item> <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item> <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item> @@ -4195,16 +4195,16 @@ easier. <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item> <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item> <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item> - <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item> + <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item> <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item> <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item> <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item> <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item> <item name="materialColorErrorContainer">@color/system_error_container_light</item> <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item> - <item name="materialColorPrimaryInverse">@color/system_primary_light</item> + <item name="materialColorPrimaryInverse">@color/system_primary_dark</item> <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item> - <item name="materialColorSurfaceInverse">@color/system_surface_light</item> + <item name="materialColorSurfaceInverse">@color/system_surface_dark</item> <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item> <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item> <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item> @@ -4366,16 +4366,16 @@ easier. <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item> <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item> <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item> - <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item> + <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item> <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item> <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item> <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item> <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item> <item name="materialColorErrorContainer">@color/system_error_container_light</item> <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item> - <item name="materialColorPrimaryInverse">@color/system_primary_light</item> + <item name="materialColorPrimaryInverse">@color/system_primary_dark</item> <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item> - <item name="materialColorSurfaceInverse">@color/system_surface_light</item> + <item name="materialColorSurfaceInverse">@color/system_surface_dark</item> <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item> <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item> <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item> @@ -4475,47 +4475,47 @@ easier. <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item> <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item> - <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item> + <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item> <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item> - <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item> - <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item> - <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item> - <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item> + <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item> + <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item> + <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item> + <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item> <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item> - <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item> + <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item> <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item> - <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item> + <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item> <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item> <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item> <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item> - <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item> - <item name="materialColorErrorContainer">@color/system_error_container_dark</item> + <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item> + <item name="materialColorErrorContainer">@color/system_error_container_light</item> <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item> - <item name="materialColorPrimaryInverse">@color/system_primary_light</item> + <item name="materialColorPrimaryInverse">@color/system_primary_dark</item> <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item> - <item name="materialColorSurfaceInverse">@color/system_surface_light</item> - <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item> - <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item> + <item name="materialColorSurfaceInverse">@color/system_surface_dark</item> + <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item> + <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item> <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item> - <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item> - <item name="materialColorOnBackground">@color/system_on_background_dark</item> + <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item> + <item name="materialColorOnBackground">@color/system_on_background_light</item> <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item> - <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item> - <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item> - <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item> - <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item> - <item name="materialColorSecondary">@color/system_secondary_dark</item> - <item name="materialColorOnError">@color/system_on_error_dark</item> - <item name="materialColorSurface">@color/system_surface_dark</item> - <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item> - <item name="materialColorTertiary">@color/system_tertiary_dark</item> - <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item> - <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item> - <item name="materialColorOutline">@color/system_outline_dark</item> - <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item> - <item name="materialColorOnPrimary">@color/system_on_primary_dark</item> - <item name="materialColorOnSurface">@color/system_on_surface_dark</item> - <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item> + <item name="materialColorOnSecondary">@color/system_on_secondary_light</item> + <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item> + <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item> + <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item> + <item name="materialColorSecondary">@color/system_secondary_light</item> + <item name="materialColorOnError">@color/system_on_error_light</item> + <item name="materialColorSurface">@color/system_surface_light</item> + <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item> + <item name="materialColorTertiary">@color/system_tertiary_light</item> + <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item> + <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item> + <item name="materialColorOutline">@color/system_outline_light</item> + <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item> + <item name="materialColorOnPrimary">@color/system_on_primary_light</item> + <item name="materialColorOnSurface">@color/system_on_surface_light</item> + <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item> </style> <style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Settings.Dialog.Alert"> @@ -4570,47 +4570,47 @@ easier. <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item> <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item> - <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_dark</item> + <item name="materialColorSurfaceContainerLowest">@color/system_surface_container_lowest_light</item> <item name="materialColorOnPrimaryFixedVariant">@color/system_on_primary_fixed_variant</item> - <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_dark</item> - <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_dark</item> - <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_dark</item> - <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_dark</item> + <item name="materialColorOnSecondaryContainer">@color/system_on_secondary_container_light</item> + <item name="materialColorOnTertiaryContainer">@color/system_on_tertiary_container_light</item> + <item name="materialColorSurfaceContainerLow">@color/system_surface_container_low_light</item> + <item name="materialColorOnPrimaryContainer">@color/system_on_primary_container_light</item> <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item> - <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item> + <item name="materialColorOnErrorContainer">@color/system_on_error_container_light</item> <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item> - <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item> + <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item> <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item> <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item> <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item> - <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item> - <item name="materialColorErrorContainer">@color/system_error_container_dark</item> + <item name="materialColorSecondaryContainer">@color/system_secondary_container_light</item> + <item name="materialColorErrorContainer">@color/system_error_container_light</item> <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item> - <item name="materialColorPrimaryInverse">@color/system_primary_light</item> + <item name="materialColorPrimaryInverse">@color/system_primary_dark</item> <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item> - <item name="materialColorSurfaceInverse">@color/system_surface_light</item> - <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item> - <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item> + <item name="materialColorSurfaceInverse">@color/system_surface_dark</item> + <item name="materialColorSurfaceVariant">@color/system_surface_variant_light</item> + <item name="materialColorTertiaryContainer">@color/system_tertiary_container_light</item> <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item> - <item name="materialColorPrimaryContainer">@color/system_primary_container_dark</item> - <item name="materialColorOnBackground">@color/system_on_background_dark</item> + <item name="materialColorPrimaryContainer">@color/system_primary_container_light</item> + <item name="materialColorOnBackground">@color/system_on_background_light</item> <item name="materialColorPrimaryFixed">@color/system_primary_fixed</item> - <item name="materialColorOnSecondary">@color/system_on_secondary_dark</item> - <item name="materialColorOnTertiary">@color/system_on_tertiary_dark</item> - <item name="materialColorSurfaceDim">@color/system_surface_dim_dark</item> - <item name="materialColorSurfaceBright">@color/system_surface_bright_dark</item> - <item name="materialColorSecondary">@color/system_secondary_dark</item> - <item name="materialColorOnError">@color/system_on_error_dark</item> - <item name="materialColorSurface">@color/system_surface_dark</item> - <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_dark</item> - <item name="materialColorTertiary">@color/system_tertiary_dark</item> - <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_dark</item> - <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_dark</item> - <item name="materialColorOutline">@color/system_outline_dark</item> - <item name="materialColorOutlineVariant">@color/system_outline_variant_dark</item> - <item name="materialColorOnPrimary">@color/system_on_primary_dark</item> - <item name="materialColorOnSurface">@color/system_on_surface_dark</item> - <item name="materialColorSurfaceContainer">@color/system_surface_container_dark</item> + <item name="materialColorOnSecondary">@color/system_on_secondary_light</item> + <item name="materialColorOnTertiary">@color/system_on_tertiary_light</item> + <item name="materialColorSurfaceDim">@color/system_surface_dim_light</item> + <item name="materialColorSurfaceBright">@color/system_surface_bright_light</item> + <item name="materialColorSecondary">@color/system_secondary_light</item> + <item name="materialColorOnError">@color/system_on_error_light</item> + <item name="materialColorSurface">@color/system_surface_light</item> + <item name="materialColorSurfaceContainerHigh">@color/system_surface_container_high_light</item> + <item name="materialColorTertiary">@color/system_tertiary_light</item> + <item name="materialColorSurfaceContainerHighest">@color/system_surface_container_highest_light</item> + <item name="materialColorOnSurfaceVariant">@color/system_on_surface_variant_light</item> + <item name="materialColorOutline">@color/system_outline_light</item> + <item name="materialColorOutlineVariant">@color/system_outline_variant_light</item> + <item name="materialColorOnPrimary">@color/system_on_primary_light</item> + <item name="materialColorOnSurface">@color/system_on_surface_light</item> + <item name="materialColorSurfaceContainer">@color/system_surface_container_light</item> </style> <style name="Theme.DeviceDefault.Settings.Dialog.NoActionBar" parent="Theme.DeviceDefault.Light.Dialog.NoActionBar" /> @@ -4700,16 +4700,16 @@ easier. <item name="materialColorSecondaryFixedDim">@color/system_secondary_fixed_dim</item> <item name="materialColorOnErrorContainer">@color/system_on_error_container_dark</item> <item name="materialColorOnSecondaryFixed">@color/system_on_secondary_fixed</item> - <item name="materialColorOnSurfaceInverse">@color/system_on_surface_dark</item> + <item name="materialColorOnSurfaceInverse">@color/system_on_surface_light</item> <item name="materialColorTertiaryFixedDim">@color/system_tertiary_fixed_dim</item> <item name="materialColorOnTertiaryFixed">@color/system_on_tertiary_fixed</item> <item name="materialColorPrimaryFixedDim">@color/system_primary_fixed_dim</item> <item name="materialColorSecondaryContainer">@color/system_secondary_container_dark</item> <item name="materialColorErrorContainer">@color/system_error_container_dark</item> <item name="materialColorOnPrimaryFixed">@color/system_on_primary_fixed</item> - <item name="materialColorPrimaryInverse">@color/system_primary_dark</item> + <item name="materialColorPrimaryInverse">@color/system_primary_light</item> <item name="materialColorSecondaryFixed">@color/system_secondary_fixed</item> - <item name="materialColorSurfaceInverse">@color/system_surface_dark</item> + <item name="materialColorSurfaceInverse">@color/system_surface_light</item> <item name="materialColorSurfaceVariant">@color/system_surface_variant_dark</item> <item name="materialColorTertiaryContainer">@color/system_tertiary_container_dark</item> <item name="materialColorTertiaryFixed">@color/system_tertiary_fixed</item> diff --git a/core/res/res/xml/irq_device_map.xml b/core/res/res/xml/irq_device_map.xml index 4fae8fb77687..8b3667e9f2a9 100644 --- a/core/res/res/xml/irq_device_map.xml +++ b/core/res/res/xml/irq_device_map.xml @@ -18,14 +18,16 @@ --> <irq-device-map> <!-- This file maps devices (chips) that can send interrupts to the main processor (and bring it - out of sleep) to logical subsystems in userspace code. Since each Android device has its own - uniquely designed chipset, this mapping is expected to be empty by default and should be - overridden by device-specific configs. + out of sleep) to logical subsystems in userspace code. Since each Android device can have + a differently designed chipset, this mapping is expected to be empty by default and should + be overridden by device-specific configs. This mapping helps the system to meaningfully attribute CPU wakeups to logical work that - happened on the device. The devices are referred to by their names as defined in the kernel. - Currently, defined subsystems are: - - Alarm: Use this to denote wakeup alarms requested by apps via the AlarmManager API. - - Wifi: Use this to denote network traffic that uses the wifi transport. + happened on the device and the app activity that caused it. The devices are referred to by + their names as defined in the kernel. Currently, defined subsystems are: + - Alarm: Use this to denote wakeup alarms requested by apps via the AlarmManager API. + - Wifi: Use this to denote network traffic that uses the wifi transport. + - Sound_trigger: Use this to denote sound phrase detection, like the ones supported by + SoundTriggerManager. The overlay should use tags <device> and <subsystem> to describe this mapping in the following way: diff --git a/core/tests/coretests/src/android/app/OWNERS b/core/tests/coretests/src/android/app/OWNERS index b3f399363aef..8d9461d8035d 100644 --- a/core/tests/coretests/src/android/app/OWNERS +++ b/core/tests/coretests/src/android/app/OWNERS @@ -4,3 +4,6 @@ per-file Window*.java = file:/services/core/java/com/android/server/wm/OWNERS per-file *Notification* = file:/packages/SystemUI/OWNERS per-file *Zen* = file:/packages/SystemUI/OWNERS per-file *StatusBar* = file:/packages/SystemUI/OWNERS + +# A11Y and related +per-file *UiAutomation* = file:/services/accessibility/OWNERS diff --git a/core/tests/coretests/src/android/app/UiAutomationTest.java b/core/tests/coretests/src/android/app/UiAutomationTest.java new file mode 100644 index 000000000000..3ac5057bcf24 --- /dev/null +++ b/core/tests/coretests/src/android/app/UiAutomationTest.java @@ -0,0 +1,144 @@ +/* + * 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.app; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.INVALID_DISPLAY; + +import static com.google.common.truth.Truth.assertWithMessage; + +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.Looper; +import android.os.UserManager; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public final class UiAutomationTest { + + private static final int SECONDARY_DISPLAY_ID = 42; + private static final int DISPLAY_ID_ASSIGNED_TO_USER = 108; + + private final Looper mLooper = Looper.getMainLooper(); + + @Mock + private Context mContext; + @Mock + private UserManager mUserManager; + @Mock + private IUiAutomationConnection mConnection; + + @Before + public void setFixtures() { + when(mContext.getMainLooper()).thenReturn(mLooper); + mockSystemService(UserManager.class, mUserManager); + + // Set default expectations + mockVisibleBackgroundUsersSupported(/* supported= */ false); + mockUserVisibility(/* visible= */ true); + // make sure it's not used, unless explicitly mocked + mockDisplayAssignedToUser(INVALID_DISPLAY); + mockContextDisplay(DEFAULT_DISPLAY); + } + + @Test + public void testContextConstructor_nullContext() { + assertThrows(IllegalArgumentException.class, + () -> new UiAutomation((Context) null, mConnection)); + } + + @Test + public void testContextConstructor_nullConnection() { + assertThrows(IllegalArgumentException.class, + () -> new UiAutomation(mContext, (IUiAutomationConnection) null)); + } + + @Test + public void testGetDisplay_contextWithSecondaryDisplayId() { + mockContextDisplay(SECONDARY_DISPLAY_ID); + + UiAutomation uiAutomation = new UiAutomation(mContext, mConnection); + + // It's always DEFAULT_DISPLAY regardless, unless the device supports visible bg users + assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId()) + .isEqualTo(DEFAULT_DISPLAY); + } + + @Test + public void testGetDisplay_contextWithInvalidDisplayId() { + mockContextDisplay(INVALID_DISPLAY); + + UiAutomation uiAutomation = new UiAutomation(mContext, mConnection); + + assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId()) + .isEqualTo(DEFAULT_DISPLAY); + } + + @Test + public void testGetDisplay_visibleBgUsers() { + mockVisibleBackgroundUsersSupported(/* supported= */ true); + mockContextDisplay(SECONDARY_DISPLAY_ID); + // Should be using display from context, not from user + mockDisplayAssignedToUser(DISPLAY_ID_ASSIGNED_TO_USER); + + UiAutomation uiAutomation = new UiAutomation(mContext, mConnection); + + assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId()) + .isEqualTo(SECONDARY_DISPLAY_ID); + } + + @Test + public void testGetDisplay_visibleBgUsers_contextWithInvalidDisplayId() { + mockVisibleBackgroundUsersSupported(/* supported= */ true); + mockContextDisplay(INVALID_DISPLAY); + mockDisplayAssignedToUser(DISPLAY_ID_ASSIGNED_TO_USER); + + UiAutomation uiAutomation = new UiAutomation(mContext, mConnection); + + assertWithMessage("getDisplayId()").that(uiAutomation.getDisplayId()) + .isEqualTo(DISPLAY_ID_ASSIGNED_TO_USER); + } + + private <T> void mockSystemService(Class<T> svcClass, T svc) { + String svcName = svcClass.getName(); + when(mContext.getSystemServiceName(svcClass)).thenReturn(svcName); + when(mContext.getSystemService(svcName)).thenReturn(svc); + } + + private void mockVisibleBackgroundUsersSupported(boolean supported) { + when(mUserManager.isVisibleBackgroundUsersSupported()).thenReturn(supported); + } + + private void mockContextDisplay(int displayId) { + when(mContext.getDisplayId()).thenReturn(displayId); + } + + private void mockDisplayAssignedToUser(int displayId) { + when(mUserManager.getMainDisplayIdAssignedToUser()).thenReturn(displayId); + } + + private void mockUserVisibility(boolean visible) { + when(mUserManager.isUserVisible()).thenReturn(visible); + } +} diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml index 2fd65dc29363..cea535e8f480 100644 --- a/data/etc/preinstalled-packages-platform-overlays.xml +++ b/data/etc/preinstalled-packages-platform-overlays.xml @@ -58,6 +58,7 @@ </install-in-user-type> <install-in-user-type package="com.android.role.notes.enabled"> <install-in user-type="FULL" /> + <install-in user-type="PROFILE" /> </install-in-user-type> <install-in-user-type package="com.android.theme.color.amethyst"> <install-in user-type="FULL" /> diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 0eb4caaf7a0f..7434cb02cc7c 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -175,12 +175,6 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, - "-1941440781": { - "message": "Creating Pending Move-to-back: %s", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_TRANSITIONS", - "at": "com\/android\/server\/wm\/Task.java" - }, "-1939861963": { "message": "Create root task displayId=%d winMode=%d", "level": "VERBOSE", @@ -631,12 +625,6 @@ "group": "WM_DEBUG_CONFIGURATION", "at": "com\/android\/server\/wm\/ActivityStarter.java" }, - "-1484988952": { - "message": "Creating Pending Multiwindow Fullscreen Request: %s", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_TRANSITIONS", - "at": "com\/android\/server\/wm\/ActivityClientController.java" - }, "-1483435730": { "message": "InsetsSource setWin %s for type %s", "level": "DEBUG", @@ -2605,12 +2593,6 @@ "group": "WM_DEBUG_ANIM", "at": "com\/android\/server\/wm\/WindowState.java" }, - "286170861": { - "message": "Creating Pending Transition for TaskFragment: %s", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_TRANSITIONS", - "at": "com\/android\/server\/wm\/WindowOrganizerController.java" - }, "288485303": { "message": "Attempted to set remove mode to a display that does not exist: %d", "level": "WARN", @@ -3193,12 +3175,6 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, - "800698875": { - "message": "SyncGroup %d: Started when there is other active SyncGroup", - "level": "WARN", - "group": "WM_DEBUG_SYNC_ENGINE", - "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" - }, "801521566": { "message": "Content Recording: Attempting to mirror %d from %d but no DisplayContent associated. Changing to mirror default display.", "level": "WARN", @@ -3259,12 +3235,6 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, - "898260097": { - "message": "Creating Pending Pip-Enter: %s", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_TRANSITIONS", - "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" - }, "898863925": { "message": "Attempted to add QS dialog window with unknown token %s. Aborting.", "level": "WARN", @@ -4009,12 +3979,6 @@ "group": "WM_DEBUG_CONTENT_RECORDING", "at": "com\/android\/server\/wm\/ContentRecorder.java" }, - "1667162379": { - "message": "Creating Pending Transition: %s", - "level": "VERBOSE", - "group": "WM_DEBUG_WINDOW_TRANSITIONS", - "at": "com\/android\/server\/wm\/WindowOrganizerController.java" - }, "1670933628": { "message": " Setting allReady override", "level": "VERBOSE", @@ -4075,6 +4039,12 @@ "group": "WM_DEBUG_SYNC_ENGINE", "at": "com\/android\/server\/wm\/BLASTSyncEngine.java" }, + "1735199721": { + "message": "Queueing transition: %s", + "level": "VERBOSE", + "group": "WM_DEBUG_WINDOW_TRANSITIONS_MIN", + "at": "com\/android\/server\/wm\/TransitionController.java" + }, "1739298851": { "message": "removeWindowToken: Attempted to remove token: %s for non-exiting displayId=%d", "level": "WARN", diff --git a/data/keyboards/GoogleTV-Remote.idc b/data/keyboards/GoogleTV-Remote.idc new file mode 100644 index 000000000000..14fb4e29c33c --- /dev/null +++ b/data/keyboards/GoogleTV-Remote.idc @@ -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. + +# +# Input Device Configuration file for Google Reference Remote Control Unit (RCU). +# +# + +# Basic Parameters +# Depending on the FLASH configurations, RCUs may have PID 0006 instead +# of 0001. +keyboard.layout = Vendor_0957_Product_0001 +keyboard.doNotWakeByDefault = 1 +audio.mic = 1 diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 9940ca3933c8..c52f700ef4f6 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -16,6 +16,7 @@ package android.graphics; +import android.annotation.NonNull; import android.view.Surface; import android.view.SurfaceControl; @@ -31,9 +32,10 @@ public final class BLASTBufferQueue { private static native long nativeCreate(String name, boolean updateDestinationFrame); private static native void nativeDestroy(long ptr); private static native Surface nativeGetSurface(long ptr, boolean includeSurfaceControlHandle); - private static native void nativeSyncNextTransaction(long ptr, + private static native boolean nativeSyncNextTransaction(long ptr, Consumer<SurfaceControl.Transaction> callback, boolean acquireSingleBuffer); private static native void nativeStopContinuousSyncTransaction(long ptr); + private static native void nativeClearSyncTransaction(long ptr); private static native void nativeUpdate(long ptr, long surfaceControl, long width, long height, int format); private static native void nativeMergeWithNextTransaction(long ptr, long transactionPtr, @@ -92,9 +94,9 @@ public final class BLASTBufferQueue { * acquired. If false, continue to acquire all buffers into the * transaction until stopContinuousSyncTransaction is called. */ - public void syncNextTransaction(boolean acquireSingleBuffer, - Consumer<SurfaceControl.Transaction> callback) { - nativeSyncNextTransaction(mNativeObject, callback, acquireSingleBuffer); + public boolean syncNextTransaction(boolean acquireSingleBuffer, + @NonNull Consumer<SurfaceControl.Transaction> callback) { + return nativeSyncNextTransaction(mNativeObject, callback, acquireSingleBuffer); } /** @@ -104,8 +106,8 @@ public final class BLASTBufferQueue { * @param callback The callback invoked when the buffer has been added to the transaction. The * callback will contain the transaction with the buffer. */ - public void syncNextTransaction(Consumer<SurfaceControl.Transaction> callback) { - syncNextTransaction(true /* acquireSingleBuffer */, callback); + public boolean syncNextTransaction(@NonNull Consumer<SurfaceControl.Transaction> callback) { + return syncNextTransaction(true /* acquireSingleBuffer */, callback); } /** @@ -118,6 +120,14 @@ public final class BLASTBufferQueue { } /** + * Tell BBQ to clear the sync transaction that was previously set. The callback will not be + * invoked when the next frame is acquired. + */ + public void clearSyncTransaction() { + nativeClearSyncTransaction(mNativeObject); + } + + /** * Updates {@link SurfaceControl}, size, and format for a particular BLASTBufferQueue * @param sc The new SurfaceControl that this BLASTBufferQueue will update * @param width The new width for the buffer. diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java index 575b0cea78f7..cc46a4bc4ea3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java @@ -129,12 +129,9 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, * {@link WindowAreaComponent#STATUS_AVAILABLE} or * {@link WindowAreaComponent#STATUS_UNAVAILABLE} if the feature is supported or not in that * state respectively. When the rear display feature is triggered, the status is updated to be - * {@link WindowAreaComponent#STATUS_UNAVAILABLE}. + * {@link WindowAreaComponent#STATUS_ACTIVE}. * TODO(b/240727590): Prefix with AREA_ * - * TODO(b/239833099): Add a STATUS_ACTIVE option to let apps know if a feature is currently - * enabled. - * * @param consumer {@link Consumer} interested in receiving updates to the status of * rear display mode. */ @@ -407,18 +404,21 @@ public class WindowAreaComponentImpl implements WindowAreaComponent, } } - @GuardedBy("mLock") private int getCurrentRearDisplayModeStatus() { if (mRearDisplayState == INVALID_DEVICE_STATE) { return WindowAreaComponent.STATUS_UNSUPPORTED; } + if (!ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState)) { + return WindowAreaComponent.STATUS_UNAVAILABLE; + } + if (mRearDisplaySessionStatus == WindowAreaComponent.SESSION_STATE_ACTIVE - || !ArrayUtils.contains(mCurrentSupportedDeviceStates, mRearDisplayState) || isRearDisplayActive()) { - return WindowAreaComponent.STATUS_UNAVAILABLE; + return WindowAreaComponent.STATUS_ACTIVE; } + return WindowAreaComponent.STATUS_AVAILABLE; } diff --git a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml index 8d1da0f7ad1b..298ad3025b00 100644 --- a/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml +++ b/libs/WindowManager/Shell/res/layout/bubble_manage_menu.xml @@ -63,11 +63,11 @@ android:tint="@color/bubbles_icon_tint"/> <TextView - android:id="@+id/bubble_manage_menu_dont_bubble_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" - android:textAppearance="@*android:style/TextAppearance.DeviceDefault" /> + android:textAppearance="@*android:style/TextAppearance.DeviceDefault" + android:text="@string/bubbles_dont_bubble_conversation" /> </LinearLayout> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 87a7c3edf826..b192fdf245e2 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -146,8 +146,6 @@ <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string> <!-- Text used for the bubble dismiss area. Bubbles dragged to, or flung towards, this area will go away. [CHAR LIMIT=30] --> <string name="bubble_dismiss_text">Dismiss bubble</string> - <!-- Button text to stop an app from bubbling [CHAR LIMIT=60]--> - <string name="bubbles_dont_bubble">Don\u2019t bubble</string> <!-- Button text to stop a conversation from bubbling [CHAR LIMIT=60]--> <string name="bubbles_dont_bubble_conversation">Don\u2019t bubble conversation</string> <!-- Title text for the bubbles feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]--> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java index 521a65cc4df6..bfbddbbe4aa0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java @@ -22,6 +22,7 @@ import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static java.util.Objects.requireNonNull; import android.content.Context; +import android.graphics.Rect; import android.os.IBinder; import android.util.ArrayMap; import android.view.SurfaceControl; @@ -35,6 +36,9 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.util.TransitionUtil; + +import java.util.List; /** * Responsible for handling ActivityEmbedding related transitions. @@ -86,12 +90,13 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback) { boolean containsEmbeddingSplit = false; - for (TransitionInfo.Change change : info.getChanges()) { + boolean containsNonEmbeddedChange = false; + final List<TransitionInfo.Change> changes = info.getChanges(); + for (int i = changes.size() - 1; i >= 0; i--) { + final TransitionInfo.Change change = changes.get(i); if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { - // Only animate the transition if all changes are in a Task with ActivityEmbedding. - return false; - } - if (!containsEmbeddingSplit && !change.hasFlags(FLAG_FILLS_TASK)) { + containsNonEmbeddedChange = true; + } else if (!change.hasFlags(FLAG_FILLS_TASK)) { // Whether the Task contains any ActivityEmbedding split before or after the // transition. containsEmbeddingSplit = true; @@ -103,6 +108,9 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle // such as the device is in a folded state. return false; } + if (containsNonEmbeddedChange && !handleNonEmbeddedChanges(changes)) { + return false; + } // Start ActivityEmbedding animation. mTransitionCallbacks.put(transition, finishCallback); @@ -110,6 +118,37 @@ public class ActivityEmbeddingController implements Transitions.TransitionHandle return true; } + private boolean handleNonEmbeddedChanges(List<TransitionInfo.Change> changes) { + final Rect nonClosingEmbeddedArea = new Rect(); + for (int i = changes.size() - 1; i >= 0; i--) { + final TransitionInfo.Change change = changes.get(i); + if (!TransitionUtil.isClosingType(change.getMode())) { + if (change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + nonClosingEmbeddedArea.union(change.getEndAbsBounds()); + continue; + } + // Not able to handle non-embedded container if it is not closing. + return false; + } + } + for (int i = changes.size() - 1; i >= 0; i--) { + final TransitionInfo.Change change = changes.get(i); + if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY) + && !nonClosingEmbeddedArea.contains(change.getEndAbsBounds())) { + // Unknown to animate containers outside the area of embedded activities. + return false; + } + } + // Drop the non-embedded closing change because it is occluded by embedded activities. + for (int i = changes.size() - 1; i >= 0; i--) { + final TransitionInfo.Change change = changes.get(i); + if (!change.hasFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + changes.remove(i); + } + } + return true; + } + @Nullable @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index 66241628fc77..1b20f67e42ab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -842,7 +842,7 @@ public class BubbleStackView extends FrameLayout private DismissView mDismissView; private ViewGroup mManageMenu; - private TextView mManageDontBubbleText; + private ViewGroup mManageDontBubbleView; private ViewGroup mManageSettingsView; private ImageView mManageSettingsIcon; private TextView mManageSettingsText; @@ -1217,8 +1217,8 @@ public class BubbleStackView extends FrameLayout mUnbubbleConversationCallback.accept(mBubbleData.getSelectedBubble().getKey()); }); - mManageDontBubbleText = mManageMenu - .findViewById(R.id.bubble_manage_menu_dont_bubble_text); + mManageDontBubbleView = mManageMenu + .findViewById(R.id.bubble_manage_menu_dont_bubble_container); mManageSettingsView = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container); mManageSettingsView.setOnClickListener( @@ -2890,14 +2890,16 @@ public class BubbleStackView extends FrameLayout final Bubble bubble = mBubbleData.getBubbleInStackWithKey(mExpandedBubble.getKey()); if (bubble != null && !bubble.isAppBubble()) { // Setup options for non app bubbles - mManageDontBubbleText.setText(R.string.bubbles_dont_bubble_conversation); + mManageDontBubbleView.setVisibility(VISIBLE); mManageSettingsIcon.setImageBitmap(bubble.getRawAppBadge()); mManageSettingsText.setText(getResources().getString( R.string.bubbles_app_settings, bubble.getAppName())); mManageSettingsView.setVisibility(VISIBLE); } else { // Setup options for app bubbles - mManageDontBubbleText.setText(R.string.bubbles_dont_bubble); + // App bubbles have no conversations + // so we don't show the option to not bubble conversation + mManageDontBubbleView.setVisibility(GONE); // App bubbles are not notification based // so we don't show the option to go to notification settings mManageSettingsView.setVisibility(GONE); @@ -2966,6 +2968,15 @@ public class BubbleStackView extends FrameLayout } /** + * Checks whether manage menu don't bubble conversation action is available and visible + * Used for testing + */ + @VisibleForTesting + public boolean isManageMenuDontBubbleVisible() { + return mManageDontBubbleView != null && mManageDontBubbleView.getVisibility() == VISIBLE; + } + + /** * Checks whether manage menu notification settings action is available and visible * Used for testing */ 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 8c98c77a29ce..1d7e64988359 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 @@ -300,7 +300,7 @@ public class PipAnimationController { * @return true if handled by the handler, false otherwise. */ public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, - Rect destinationBounds) { + Rect destinationBounds, float alpha) { return false; } } @@ -401,9 +401,10 @@ public class PipAnimationController { } boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, - Rect destinationBounds) { + Rect destinationBounds, float alpha) { if (mPipTransactionHandler != null) { - return mPipTransactionHandler.handlePipTransaction(leash, tx, destinationBounds); + return mPipTransactionHandler.handlePipTransaction( + leash, tx, destinationBounds, alpha); } return false; } @@ -548,7 +549,9 @@ public class PipAnimationController { getSurfaceTransactionHelper().alpha(tx, leash, alpha) .round(tx, leash, shouldApplyCornerRadius()) .shadow(tx, leash, shouldApplyShadowRadius()); - tx.apply(); + if (!handlePipTransaction(leash, tx, destinationBounds, alpha)) { + tx.apply(); + } } @Override @@ -663,7 +666,7 @@ public class PipAnimationController { .shadow(tx, leash, shouldApplyShadowRadius()); } } - if (!handlePipTransaction(leash, tx, bounds)) { + if (!handlePipTransaction(leash, tx, bounds, /* alpha= */ 1f)) { tx.apply(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java index 000624499f79..0775f5279e31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java @@ -45,6 +45,13 @@ public interface PipMenuController { String MENU_WINDOW_TITLE = "PipMenuView"; /** + * Used with + * {@link PipMenuController#movePipMenu(SurfaceControl, SurfaceControl.Transaction, Rect, + * float)} to indicate that we don't want to affect the alpha value of the menu surfaces. + */ + float ALPHA_NO_CHANGE = -1f; + + /** * Called when * {@link PipTaskOrganizer#onTaskAppeared(RunningTaskInfo, SurfaceControl)} * is called. @@ -85,8 +92,8 @@ public interface PipMenuController { * need to synchronize the movements on the same frame as PiP. */ default void movePipMenu(@Nullable SurfaceControl pipLeash, - @Nullable SurfaceControl.Transaction t, - Rect destinationBounds) {} + @Nullable SurfaceControl.Transaction t, Rect destinationBounds, float alpha) { + } /** * Update the PiP menu with the given bounds for re-layout purposes. 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 5670fe6eaeba..d04ce1540980 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 @@ -249,7 +249,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, }, null); } - private boolean shouldSyncPipTransactionWithMenu() { + protected boolean shouldSyncPipTransactionWithMenu() { return mPipMenuController.isMenuVisible(); } @@ -277,9 +277,9 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, new PipAnimationController.PipTransactionHandler() { @Override public boolean handlePipTransaction(SurfaceControl leash, - SurfaceControl.Transaction tx, Rect destinationBounds) { + SurfaceControl.Transaction tx, Rect destinationBounds, float alpha) { if (shouldSyncPipTransactionWithMenu()) { - mPipMenuController.movePipMenu(leash, tx, destinationBounds); + mPipMenuController.movePipMenu(leash, tx, destinationBounds, alpha); return true; } return false; @@ -381,6 +381,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return mPipTransitionController; } + PipAnimationController.PipTransactionHandler getPipTransactionHandler() { + return mPipTransactionHandler; + } + public Rect getCurrentOrAnimatingBounds() { PipAnimationController.PipTransitionAnimator animator = mPipAnimationController.getCurrentAnimator(); @@ -712,15 +716,16 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } + final int animationType = shouldAlwaysFadeIn() + ? ANIM_TYPE_ALPHA + : mPipAnimationController.takeOneShotEnterAnimationType(); if (Transitions.ENABLE_SHELL_TRANSITIONS) { + mPipTransitionController.setEnterAnimationType(animationType); // For Shell transition, we will animate the window in PipTransition#startAnimation // instead of #onTaskAppeared. return; } - final int animationType = shouldAlwaysFadeIn() - ? ANIM_TYPE_ALPHA - : mPipAnimationController.takeOneShotEnterAnimationType(); if (mWaitForFixedRotation) { onTaskAppearedWithFixedRotation(animationType); return; @@ -1385,7 +1390,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, .scale(tx, mLeash, startBounds, toBounds, degrees) .round(tx, mLeash, startBounds, toBounds); if (shouldSyncPipTransactionWithMenu()) { - mPipMenuController.movePipMenu(mLeash, tx, toBounds); + mPipMenuController.movePipMenu(mLeash, tx, toBounds, PipMenuController.ALPHA_NO_CHANGE); } else { tx.apply(); } @@ -1551,7 +1556,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (!isInPip()) { return; } - mPipMenuController.movePipMenu(null, null, destinationBounds); + mPipMenuController.movePipMenu(null, null, destinationBounds, + PipMenuController.ALPHA_NO_CHANGE); mPipMenuController.updateMenuBounds(destinationBounds); } 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 b743140b2403..5755a10897af 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 @@ -274,9 +274,6 @@ public class PipTransition extends PipTransitionController { if (!requestHasPipEnter(request)) { throw new IllegalStateException("Called PiP augmentRequest when request has no PiP"); } - mEnterAnimationType = mPipOrganizer.shouldAlwaysFadeIn() - ? ANIM_TYPE_ALPHA - : mPipAnimationController.takeOneShotEnterAnimationType(); if (mEnterAnimationType == ANIM_TYPE_ALPHA) { mRequestedEnterTransition = transition; mRequestedEnterTask = request.getTriggerTask().token; @@ -614,6 +611,7 @@ public class PipTransition extends PipTransitionController { 0 /* startingAngle */, rotationDelta); animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP) .setPipAnimationCallback(mPipAnimationCallback) + .setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler()) .setDuration(mEnterExitAnimationDuration) .start(); } @@ -665,6 +663,11 @@ public class PipTransition extends PipTransitionController { return false; } + @Override + public void setEnterAnimationType(@PipAnimationController.AnimationType int type) { + mEnterAnimationType = type; + } + private void startEnterAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @@ -786,8 +789,6 @@ public class PipTransition extends PipTransitionController { final int enterAnimationType = mEnterAnimationType; if (enterAnimationType == ANIM_TYPE_ALPHA) { - // Restore to default type. - mEnterAnimationType = ANIM_TYPE_BOUNDS; startTransaction.setAlpha(leash, 0f); } startTransaction.apply(); @@ -823,6 +824,7 @@ public class PipTransition extends PipTransitionController { throw new RuntimeException("Unrecognized animation type: " + enterAnimationType); } animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) + .setPipTransactionHandler(mPipOrganizer.getPipTransactionHandler()) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration); if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { @@ -949,7 +951,8 @@ public class PipTransition extends PipTransitionController { } private void finishResizeForMenu(Rect destinationBounds) { - mPipMenuController.movePipMenu(null, null, destinationBounds); + mPipMenuController.movePipMenu(null, null, destinationBounds, + PipMenuController.ALPHA_NO_CHANGE); mPipMenuController.updateMenuBounds(destinationBounds); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 7979ce7a80c1..ff7ab8ba1d97 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -225,6 +225,10 @@ public abstract class PipTransitionController implements Transitions.TransitionH throw new IllegalStateException("Request isn't entering PiP"); } + /** Sets the type of animation when a PiP task appears. */ + public void setEnterAnimationType(@PipAnimationController.AnimationType int type) { + } + /** Play a transition animation for entering PiP on a specific PiP change. */ public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange, @NonNull final SurfaceControl.Transaction startTransaction, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 94e593b106a5..e7a1395f541c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -298,7 +298,8 @@ public class PhonePipMenuController implements PipMenuController { } // Sync the menu bounds before showing it in case it is out of sync. - movePipMenu(null /* pipLeash */, null /* transaction */, stackBounds); + movePipMenu(null /* pipLeash */, null /* transaction */, stackBounds, + PipMenuController.ALPHA_NO_CHANGE); updateMenuBounds(stackBounds); mPipMenuView.showMenu(menuState, stackBounds, allowMenuTimeout, willResizeMenu, withDelay, @@ -311,7 +312,7 @@ public class PhonePipMenuController implements PipMenuController { @Override public void movePipMenu(@Nullable SurfaceControl pipLeash, @Nullable SurfaceControl.Transaction t, - Rect destinationBounds) { + Rect destinationBounds, float alpha) { if (destinationBounds.isEmpty()) { return; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java index fa62a73ca9b4..3b44f10ebe62 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipActionsProvider.java @@ -186,16 +186,17 @@ public class TvPipActionsProvider implements TvPipAction.SystemActionsHandler { } @VisibleForTesting(visibility = PACKAGE) - public void onPipExpansionToggled(boolean expanded) { + public void updatePipExpansionState(boolean expanded) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: onPipExpansionToggled, expanded: %b", TAG, expanded); - mExpandCollapseAction.update( + boolean changed = mExpandCollapseAction.update( expanded ? R.string.pip_collapse : R.string.pip_expand, expanded ? R.drawable.pip_ic_collapse : R.drawable.pip_ic_expand); - - notifyActionsChanged(/* added= */ 0, /* updated= */ 1, - mActionsList.indexOf(mExpandCollapseAction)); + if (changed) { + notifyActionsChanged(/* added= */ 0, /* updated= */ 1, + mActionsList.indexOf(mExpandCollapseAction)); + } } List<TvPipAction> getActionsList() { 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 2f74fb636bc9..02eeb2ac4fd5 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 @@ -502,7 +502,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal "%s: onPipTransition_Canceled(), state=%s", TAG, stateToName(mState)); mTvPipMenuController.onPipTransitionFinished( PipAnimationController.isInPipDirection(direction)); - mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded()); + mTvPipActionsProvider.updatePipExpansionState(mTvPipBoundsState.isTvPipExpanded()); } @Override @@ -515,7 +515,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal "%s: onPipTransition_Finished(), state=%s, direction=%d", TAG, stateToName(mState), direction); mTvPipMenuController.onPipTransitionFinished(enterPipTransition); - mTvPipActionsProvider.onPipExpansionToggled(mTvPipBoundsState.isTvPipExpanded()); + mTvPipActionsProvider.updatePipExpansionState(mTvPipBoundsState.isTvPipExpanded()); } private void updateExpansionState() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java index b18e21c03c63..b2a189b45d6c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java @@ -30,6 +30,7 @@ import android.os.Handler; import android.view.SurfaceControl; import android.view.View; import android.view.ViewRootImpl; +import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.window.SurfaceSyncGroup; @@ -202,8 +203,10 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis } private void addPipMenuViewToSystemWindows(View v, String title) { - mSystemWindows.addView(v, getPipMenuLayoutParams(mContext, title, 0 /* width */, - 0 /* height */), 0 /* displayId */, SHELL_ROOT_LAYER_PIP); + final WindowManager.LayoutParams layoutParams = + getPipMenuLayoutParams(mContext, title, 0 /* width */, 0 /* height */); + layoutParams.alpha = 0f; + mSystemWindows.addView(v, layoutParams, 0 /* displayId */, SHELL_ROOT_LAYER_PIP); } void onPipTransitionFinished(boolean enterTransition) { @@ -309,9 +312,9 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis @Override public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction pipTx, - Rect pipBounds) { + Rect pipBounds, float alpha) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: movePipMenu: %s", TAG, pipBounds.toShortString()); + "%s: movePipMenu: %s, alpha %s", TAG, pipBounds.toShortString(), alpha); if (pipBounds.isEmpty()) { if (pipTx == null) { @@ -333,6 +336,11 @@ public class TvPipMenuController implements PipMenuController, TvPipMenuView.Lis pipTx.setPosition(frontSurface, menuDestBounds.left, menuDestBounds.top); pipTx.setPosition(backSurface, menuDestBounds.left, menuDestBounds.top); + if (alpha != ALPHA_NO_CHANGE) { + pipTx.setAlpha(frontSurface, alpha); + pipTx.setAlpha(backSurface, alpha); + } + // Synchronize drawing the content in the front and back surfaces together with the pip // transaction and the position change for the front and back surfaces final SurfaceSyncGroup syncGroup = new SurfaceSyncGroup("TvPip"); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java index 4b82e4bdb64a..f6dabb762c54 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipSystemAction.java @@ -52,9 +52,14 @@ public class TvPipSystemAction extends TvPipAction { broadcastAction); } - void update(@StringRes int title, @DrawableRes int icon) { + /** + * @return true if the title and/or icon were changed. + */ + boolean update(@StringRes int title, @DrawableRes int icon) { + boolean changed = title != mTitleResource || icon != mIconResource; mTitleResource = title; mIconResource = icon; + return changed; } void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler) { 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 0940490e9944..4819f665d6d3 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 @@ -98,4 +98,11 @@ public class TvPipTaskOrganizer extends PipTaskOrganizer { protected boolean shouldAlwaysFadeIn() { return true; } + + @Override + protected boolean shouldSyncPipTransactionWithMenu() { + // We always have a menu visible and want to sync the pip transaction with the menu, even + // when the menu alpha is 0 (e.g. when a fade-in animation starts). + return true; + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 2cd16be9590c..498f95c8595e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -541,6 +541,34 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, instanceId); } + void startShortcutAndTask(ShortcutInfo shortcutInfo, @Nullable Bundle options1, + int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, + float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + if (options1 == null) options1 = new Bundle(); + final ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); + final String packageName1 = shortcutInfo.getPackage(); + // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in + // recents that hasn't launched and is not being organized + final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer); + if (samePackage(packageName1, packageName2)) { + if (supportMultiInstancesSplit(packageName1)) { + activityOptions.setApplyMultipleTaskFlagForShortcut(true); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); + } else { + if (mRecentTasksOptional.isPresent()) { + mRecentTasksOptional.get().removeSplitPair(taskId); + } + taskId = INVALID_TASK_ID; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "Cancel entering split as not supporting multi-instances"); + Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, + Toast.LENGTH_SHORT).show(); + } + } + mStageCoordinator.startShortcutAndTask(shortcutInfo, options1, taskId, options2, + splitPosition, splitRatio, remoteTransition, instanceId); + } + /** * See {@link #startIntent(PendingIntent, Intent, int, Bundle)} * @param instanceId to be used by {@link SplitscreenEventLogger} @@ -580,6 +608,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { Intent fillInIntent = null; final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent); + // NOTE: This doesn't correctly pull out packageName2 if taskId is referring to a task in + // recents that hasn't launched and is not being organized final String packageName2 = SplitScreenUtils.getPackageName(taskId, mTaskOrganizer); if (samePackage(packageName1, packageName2)) { if (supportMultiInstancesSplit(packageName1)) { @@ -587,6 +617,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); } else { + if (mRecentTasksOptional.isPresent()) { + mRecentTasksOptional.get().removeSplitPair(taskId); + } + taskId = INVALID_TASK_ID; ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Cancel entering split as not supporting multi-instances"); Toast.makeText(mContext, R.string.dock_multi_instances_not_supported_text, @@ -1075,9 +1109,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { executeRemoteCallWithTaskPermission(mController, "startShortcutAndTask", - (controller) -> controller.mStageCoordinator.startShortcutAndTask(shortcutInfo, - options1, taskId, options2, splitPosition, splitRatio, remoteTransition, - instanceId)); + (controller) -> controller.startShortcutAndTask(shortcutInfo, options1, taskId, + options2, splitPosition, splitRatio, remoteTransition, instanceId)); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 8b890bba20b6..51b8000f14bb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -38,7 +38,6 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; -import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; @@ -139,20 +138,13 @@ class SplitScreenTransitions { t.setAlpha(parentChange.getLeash(), 1.f); // and then animate this layer outside the parent (since, for example, this is // the home task animating from fullscreen to part-screen). - t.reparent(leash, info.getRoot(rootIdx).getLeash()); - t.setLayer(leash, info.getChanges().size() - i); + t.reparent(parentChange.getLeash(), info.getRoot(rootIdx).getLeash()); + t.setLayer(parentChange.getLeash(), info.getChanges().size() - i); // build the finish reparent/reposition mFinishTransaction.reparent(leash, parentChange.getLeash()); mFinishTransaction.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y); } - // TODO(shell-transitions): screenshot here - final Rect startBounds = new Rect(change.getStartAbsBounds()); - final Rect endBounds = new Rect(change.getEndAbsBounds()); - final Point rootOffset = info.getRoot(rootIdx).getOffset(); - startBounds.offset(-rootOffset.x, -rootOffset.y); - endBounds.offset(-rootOffset.x, -rootOffset.y); - startExampleResizeAnimation(leash, startBounds, endBounds); } boolean isRootOrSplitSideRoot = change.getParent() == null || topRoot.equals(change.getParent()); @@ -289,6 +281,12 @@ class SplitScreenTransitions { return null; } + void startFullscreenTransition(WindowContainerTransaction wct, + @Nullable RemoteTransition handler) { + mTransitions.startTransition(TRANSIT_OPEN, wct, + new OneShotRemoteHandler(mTransitions.getMainExecutor(), handler)); + } + /** Starts a transition to enter split with a remote transition animator. */ IBinder startEnterTransition( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index ce5a2af65646..e4f27240b2cb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -612,6 +612,19 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (taskId2 == INVALID_TASK_ID) { + if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) { + prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct); + } + if (mRecentTasks.isPresent()) { + mRecentTasks.get().removeSplitPair(taskId1); + } + options1 = options1 != null ? options1 : new Bundle(); + wct.startTask(taskId1, options1); + mSplitTransitions.startFullscreenTransition(wct, remoteTransition); + return; + } + prepareEvictChildTasksIfSplitActive(wct); setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); @@ -627,6 +640,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (taskId == INVALID_TASK_ID) { + options1 = options1 != null ? options1 : new Bundle(); + wct.sendPendingIntent(pendingIntent, fillInIntent, options1); + mSplitTransitions.startFullscreenTransition(wct, remoteTransition); + return; + } + prepareEvictChildTasksIfSplitActive(wct); setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); @@ -641,6 +661,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, int taskId, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (taskId == INVALID_TASK_ID) { + options1 = options1 != null ? options1 : new Bundle(); + wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1); + mSplitTransitions.startFullscreenTransition(wct, remoteTransition); + return; + } + prepareEvictChildTasksIfSplitActive(wct); setSideStagePosition(splitPosition, wct); options1 = options1 != null ? options1 : new Bundle(); @@ -689,6 +716,17 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { final WindowContainerTransaction wct = new WindowContainerTransaction(); + if (pendingIntent2 == null) { + options1 = options1 != null ? options1 : new Bundle(); + if (shortcutInfo1 != null) { + wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1); + } else { + wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1); + } + mSplitTransitions.startFullscreenTransition(wct, remoteTransition); + return; + } + if (!mMainStage.isActive()) { // Build a request WCT that will launch both apps such that task 0 is on the main stage // while task 1 is on the side stage. @@ -2376,9 +2414,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) { - // TODO(shell-transitions): Implement a fallback behavior for now. - throw new IllegalStateException("Somehow removed the last task in a stage" - + " outside of a proper transition"); + Log.e(TAG, "Somehow removed the last task in a stage outside of a proper " + + "transition."); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + final int dismissTop = mMainStage.getChildCount() == 0 + ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE; + prepareExitSplitScreen(dismissTop, wct); + mSplitTransitions.startDismissTransition(wct, this, dismissTop, + EXIT_REASON_UNKNOWN); // This can happen in some pathological cases. For example: // 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C] // 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time 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 3dd10a098310..6e9ecdaf55e7 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 @@ -327,6 +327,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { @ColorInt int backgroundColorForTransition = 0; final int wallpaperTransit = getWallpaperTransitType(info); + boolean isDisplayRotationAnimationStarted = false; for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY @@ -350,6 +351,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { if (!(isSeamlessDisplayChange || anim == ROTATION_ANIMATION_JUMPCUT)) { startRotationAnimation(startTransaction, change, info, anim, animations, onAnimFinish); + isDisplayRotationAnimationStarted = true; continue; } } else { @@ -405,6 +407,14 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { } } + // Hide the invisible surface directly without animating it if there is a display + // rotation animation playing. + if (isDisplayRotationAnimationStarted && TransitionUtil.isClosingType( + change.getMode())) { + startTransaction.hide(change.getLeash()); + continue; + } + // The back gesture has animated this change before transition happen, so here we don't // play the animation again. if (change.hasFlags(FLAG_BACK_GESTURE_ANIMATED)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index a004e37c6345..67e99d73b811 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -42,6 +42,7 @@ import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; +import android.window.SurfaceSyncGroup; import android.window.WindowContainerTransaction; import com.android.launcher3.icons.IconProvider; @@ -311,51 +312,50 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin * Create and display handle menu window */ void createHandleMenu() { + final SurfaceSyncGroup ssg = new SurfaceSyncGroup(TAG); final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); updateHandleMenuPillPositions(); - createAppInfoPill(t); + createAppInfoPill(t, ssg); // Only show windowing buttons in proto2. Proto1 uses a system-level mode only. final boolean shouldShowWindowingPill = DesktopModeStatus.isProto2Enabled(); if (shouldShowWindowingPill) { - createWindowingPill(t); + createWindowingPill(t, ssg); } - createMoreActionsPill(t); + createMoreActionsPill(t, ssg); - mSyncQueue.runInSync(transaction -> { - transaction.merge(t); - t.close(); - }); + ssg.addTransaction(t); + ssg.markSyncReady(); setupHandleMenu(shouldShowWindowingPill); } - private void createAppInfoPill(SurfaceControl.Transaction t) { + private void createAppInfoPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) { final int x = (int) mHandleMenuAppInfoPillPosition.x; final int y = (int) mHandleMenuAppInfoPillPosition.y; mHandleMenuAppInfoPill = addWindow( R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, "Menu's app info pill", - t, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius); + t, ssg, x, y, mMenuWidth, mAppInfoPillHeight, mShadowRadius, mCornerRadius); } - private void createWindowingPill(SurfaceControl.Transaction t) { + private void createWindowingPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) { final int x = (int) mHandleMenuWindowingPillPosition.x; final int y = (int) mHandleMenuWindowingPillPosition.y; mHandleMenuWindowingPill = addWindow( R.layout.desktop_mode_window_decor_handle_menu_windowing_pill, "Menu's windowing pill", - t, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius); + t, ssg, x, y, mMenuWidth, mWindowingPillHeight, mShadowRadius, mCornerRadius); } - private void createMoreActionsPill(SurfaceControl.Transaction t) { + private void createMoreActionsPill(SurfaceControl.Transaction t, SurfaceSyncGroup ssg) { final int x = (int) mHandleMenuMoreActionsPillPosition.x; final int y = (int) mHandleMenuMoreActionsPillPosition.y; mHandleMenuMoreActionsPill = addWindow( R.layout.desktop_mode_window_decor_handle_menu_more_actions_pill, "Menu's more actions pill", - t, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius); + t, ssg, x, y, mMenuWidth, mMoreActionsPillHeight, mShadowRadius, mCornerRadius); } private void setupHandleMenu(boolean windowingPillShown) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java index bc5fd4dcbdc8..8b35694ff8fd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java @@ -34,6 +34,7 @@ import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowlessWindowManager; +import android.window.SurfaceSyncGroup; import android.window.TaskConstants; import android.window.WindowContainerTransaction; @@ -90,7 +91,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> Display mDisplay; Context mDecorWindowContext; SurfaceControl mDecorationContainerSurface; - SurfaceControl mTaskBackgroundSurface; SurfaceControl mCaptionContainerSurface; private WindowlessWindowManager mCaptionWindowManager; @@ -192,15 +192,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> mDecorWindowContext = mContext.createConfigurationContext(taskConfig); if (params.mLayoutResId != 0) { outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) - .inflate(params.mLayoutResId, null); + .inflate(params.mLayoutResId, null); } } if (outResult.mRootView == null) { outResult.mRootView = (T) LayoutInflater.from(mDecorWindowContext) - .inflate(params.mLayoutResId , null); + .inflate(params.mLayoutResId, null); } + final Resources resources = mDecorWindowContext.getResources(); + final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); + outResult.mWidth = taskBounds.width(); + outResult.mHeight = taskBounds.height(); + // DecorationContainerSurface if (mDecorationContainerSurface == null) { final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); @@ -215,38 +220,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> TaskConstants.TASK_CHILD_LAYER_WINDOW_DECORATIONS); } - final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); - final Resources resources = mDecorWindowContext.getResources(); - outResult.mWidth = taskBounds.width(); - outResult.mHeight = taskBounds.height(); startT.setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight) .show(mDecorationContainerSurface); - // TODO(b/270202228): This surface can be removed. Instead, use - // |mDecorationContainerSurface| to set the background now that it no longer has outsets - // and its crop is set to the task bounds. - // TaskBackgroundSurface - if (mTaskBackgroundSurface == null) { - final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); - mTaskBackgroundSurface = builder - .setName("Background of Task=" + mTaskInfo.taskId) - .setEffectLayer() - .setParent(mTaskSurface) - .build(); - - startT.setLayer(mTaskBackgroundSurface, TaskConstants.TASK_CHILD_LAYER_TASK_BACKGROUND); - } - - float shadowRadius = loadDimension(resources, params.mShadowRadiusId); - int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor(); - mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f; - mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f; - mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; - startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height()) - .setShadowRadius(mTaskBackgroundSurface, shadowRadius) - .setColor(mTaskBackgroundSurface, mTmpColor) - .show(mTaskBackgroundSurface); - // CaptionContainerSurface, CaptionWindowManager if (mCaptionContainerSurface == null) { final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); @@ -259,7 +235,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); final int captionWidth = taskBounds.width(); - startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) .show(mCaptionContainerSurface); @@ -300,8 +275,16 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> } // Task surface itself + float shadowRadius = loadDimension(resources, params.mShadowRadiusId); + int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor(); + mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f; + mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f; + mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f; Point taskPosition = mTaskInfo.positionInParent; - startT.show(mTaskSurface); + startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight) + .setShadowRadius(mTaskSurface, shadowRadius) + .setColor(mTaskSurface, mTmpColor) + .show(mTaskSurface); finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); } @@ -343,12 +326,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> released = true; } - if (mTaskBackgroundSurface != null) { - t.remove(mTaskBackgroundSurface); - mTaskBackgroundSurface = null; - released = true; - } - if (released) { t.apply(); } @@ -382,18 +359,20 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> /** * Create a window associated with this WindowDecoration. * Note that subclass must dispose of this when the task is hidden/closed. - * @param layoutId layout to make the window from - * @param t the transaction to apply - * @param xPos x position of new window - * @param yPos y position of new window - * @param width width of new window - * @param height height of new window + * + * @param layoutId layout to make the window from + * @param t the transaction to apply + * @param xPos x position of new window + * @param yPos y position of new window + * @param width width of new window + * @param height height of new window * @param shadowRadius radius of the shadow of the new window * @param cornerRadius radius of the corners of the new window * @return the {@link AdditionalWindow} that was added. */ AdditionalWindow addWindow(int layoutId, String namePrefix, SurfaceControl.Transaction t, - int xPos, int yPos, int width, int height, int shadowRadius, int cornerRadius) { + SurfaceSyncGroup ssg, int xPos, int yPos, int width, int height, int shadowRadius, + int cornerRadius) { final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get(); SurfaceControl windowSurfaceControl = builder .setName(namePrefix + " of Task=" + mTaskInfo.taskId) @@ -417,12 +396,12 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> windowSurfaceControl, null /* hostInputToken */); SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory .create(mDecorWindowContext, mDisplay, windowManager); - viewHost.setView(v, lp); + ssg.add(viewHost.getSurfacePackage(), () -> viewHost.setView(v, lp)); return new AdditionalWindow(windowSurfaceControl, viewHost, mSurfaceControlTransactionSupplier); } - static class RelayoutParams{ + static class RelayoutParams { RunningTaskInfo mRunningTaskInfo; int mLayoutResId; int mCaptionHeightId; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java index cbbb29199d75..b8f615a1855f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/activityembedding/ActivityEmbeddingControllerTests.java @@ -16,6 +16,7 @@ package com.android.wm.shell.activityembedding; +import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; @@ -82,10 +83,13 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation @Test public void testStartAnimation_containsNonActivityEmbeddingChange() { + final TransitionInfo.Change nonEmbeddedOpen = createChange(0 /* flags */); + final TransitionInfo.Change embeddedOpen = createEmbeddedChange( + EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS); + nonEmbeddedOpen.setMode(TRANSIT_OPEN); final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN, 0) - .addChange(createEmbeddedChange( - EMBEDDED_LEFT_BOUNDS, EMBEDDED_LEFT_BOUNDS, TASK_BOUNDS)) - .addChange(createChange(0 /* flags */)) + .addChange(embeddedOpen) + .addChange(nonEmbeddedOpen) .build(); // No-op because it contains non-embedded change. @@ -95,6 +99,22 @@ public class ActivityEmbeddingControllerTests extends ActivityEmbeddingAnimation verifyNoMoreInteractions(mStartTransaction); verifyNoMoreInteractions(mFinishTransaction); verifyNoMoreInteractions(mFinishCallback); + + final TransitionInfo.Change nonEmbeddedClose = createChange(0 /* flags */); + nonEmbeddedClose.setMode(TRANSIT_CLOSE); + nonEmbeddedClose.setEndAbsBounds(TASK_BOUNDS); + final TransitionInfo.Change embeddedOpen2 = createEmbeddedChange( + EMBEDDED_RIGHT_BOUNDS, EMBEDDED_RIGHT_BOUNDS, TASK_BOUNDS); + final TransitionInfo info2 = new TransitionInfoBuilder(TRANSIT_OPEN, 0) + .addChange(embeddedOpen) + .addChange(embeddedOpen2) + .addChange(nonEmbeddedClose) + .build(); + // Ok to animate because nonEmbeddedClose is occluded by embeddedOpen and embeddedOpen2. + assertTrue(mController.startAnimation(mTransition, info2, mStartTransaction, + mFinishTransaction, mFinishCallback)); + // The non-embedded change is dropped to avoid affecting embedded animation. + assertFalse(info2.getChanges().contains(nonEmbeddedClose)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java index e5b61ed2fd25..02e6b8c71663 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/tv/TvPipActionProviderTest.java @@ -158,19 +158,73 @@ public class TvPipActionProviderTest extends ShellTestCase { new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE})); } - @Test - public void expandedPip_toggleExpansion() { - assumeTelevision(); - // PiP has expanded PiP enabled, but is in a collapsed state + private void check_expandedPip_updateExpansionState( + boolean startExpansion, boolean endExpansion, boolean updateExpected) { + mActionsProvider.updateExpansionEnabled(true); - mActionsProvider.onPipExpansionToggled(/* expanded= */ false); + mActionsProvider.updatePipExpansionState(startExpansion); mActionsProvider.addListener(mMockListener); - mActionsProvider.onPipExpansionToggled(/* expanded= */ true); + mActionsProvider.updatePipExpansionState(endExpansion); assertTrue(checkActionsMatch(mActionsProvider.getActionsList(), new int[]{ACTION_FULLSCREEN, ACTION_CLOSE, ACTION_MOVE, ACTION_EXPAND_COLLAPSE})); - verify(mMockListener).onActionsChanged(0, 1, 3); + + if (updateExpected) { + verify(mMockListener).onActionsChanged(0, 1, 3); + } else { + verify(mMockListener, times(0)) + .onActionsChanged(anyInt(), anyInt(), anyInt()); + } + } + + @Test + public void expandedPip_toggleExpansion_collapse() { + assumeTelevision(); + check_expandedPip_updateExpansionState( + /* startExpansion= */ true, + /* endExpansion= */ false, + /* updateExpected= */ true); + } + + @Test + public void expandedPip_toggleExpansion_expand() { + assumeTelevision(); + check_expandedPip_updateExpansionState( + /* startExpansion= */ false, + /* endExpansion= */ true, + /* updateExpected= */ true); + } + + @Test + public void expandedPiP_updateExpansionState_alreadyExpanded() { + assumeTelevision(); + check_expandedPip_updateExpansionState( + /* startExpansion= */ true, + /* endExpansion= */ true, + /* updateExpected= */ false); + } + + @Test + public void expandedPiP_updateExpansionState_alreadyCollapsed() { + assumeTelevision(); + check_expandedPip_updateExpansionState( + /* startExpansion= */ false, + /* endExpansion= */ false, + /* updateExpected= */ false); + } + + @Test + public void regularPiP_updateExpansionState_setCollapsed() { + assumeTelevision(); + mActionsProvider.updateExpansionEnabled(false); + mActionsProvider.updatePipExpansionState(/* expanded= */ false); + + mActionsProvider.addListener(mMockListener); + mActionsProvider.updatePipExpansionState(/* expanded= */ false); + + verify(mMockListener, times(0)) + .onActionsChanged(anyInt(), anyInt(), anyInt()); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java index e8147ff264cc..c1e53a90b7e0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java @@ -49,7 +49,7 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager.LayoutParams; -import android.window.TaskConstants; +import android.window.SurfaceSyncGroup; import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; @@ -100,6 +100,8 @@ public class WindowDecorationTests extends ShellTestCase { private TestView mMockView; @Mock private WindowContainerTransaction mMockWindowContainerTransaction; + @Mock + private SurfaceSyncGroup mMockSurfaceSyncGroup; private final List<SurfaceControl.Transaction> mMockSurfaceControlTransactions = new ArrayList<>(); @@ -187,10 +189,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder taskBackgroundSurfaceBuilder = - createMockSurfaceControlBuilder(taskBackgroundSurface); - mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder); final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); final SurfaceControl.Builder captionContainerSurfaceBuilder = createMockSurfaceControlBuilder(captionContainerSurface); @@ -219,16 +217,6 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true); verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100); - verify(taskBackgroundSurfaceBuilder).setParent(taskSurface); - verify(taskBackgroundSurfaceBuilder).setEffectLayer(); - verify(mMockSurfaceControlStartT).setWindowCrop(taskBackgroundSurface, 300, 100); - verify(mMockSurfaceControlStartT) - .setColor(taskBackgroundSurface, new float[] {1.f, 1.f, 0.f}); - verify(mMockSurfaceControlStartT).setShadowRadius(taskBackgroundSurface, 10); - verify(mMockSurfaceControlStartT).setLayer(taskBackgroundSurface, - TaskConstants.TASK_CHILD_LAYER_TASK_BACKGROUND); - verify(mMockSurfaceControlStartT).show(taskBackgroundSurface); - verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); verify(captionContainerSurfaceBuilder).setContainerLayer(); verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); @@ -257,6 +245,9 @@ public class WindowDecorationTests extends ShellTestCase { .setWindowCrop(taskSurface, 300, 100); verify(mMockSurfaceControlStartT) .show(taskSurface); + verify(mMockSurfaceControlStartT) + .setColor(taskSurface, new float[] {1.f, 1.f, 0.f}); + verify(mMockSurfaceControlStartT).setShadowRadius(taskSurface, 10); assertEquals(300, mRelayoutResult.mWidth); assertEquals(100, mRelayoutResult.mHeight); @@ -272,10 +263,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder taskBackgroundSurfaceBuilder = - createMockSurfaceControlBuilder(taskBackgroundSurface); - mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder); final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); final SurfaceControl.Builder captionContainerSurfaceBuilder = createMockSurfaceControlBuilder(captionContainerSurface); @@ -315,7 +302,6 @@ public class WindowDecorationTests extends ShellTestCase { releaseOrder.verify(mMockSurfaceControlViewHost).release(); releaseOrder.verify(t).remove(captionContainerSurface); releaseOrder.verify(t).remove(decorContainerSurface); - releaseOrder.verify(t).remove(taskBackgroundSurface); releaseOrder.verify(t).apply(); verify(mMockWindowContainerTransaction) .removeInsetsSource(eq(taskInfo.token), any(), anyInt(), anyInt()); @@ -376,10 +362,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder taskBackgroundSurfaceBuilder = - createMockSurfaceControlBuilder(taskBackgroundSurface); - mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder); final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); final SurfaceControl.Builder captionContainerSurfaceBuilder = createMockSurfaceControlBuilder(captionContainerSurface); @@ -448,10 +430,6 @@ public class WindowDecorationTests extends ShellTestCase { final SurfaceControl.Builder decorContainerSurfaceBuilder = createMockSurfaceControlBuilder(decorContainerSurface); mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder); - final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class); - final SurfaceControl.Builder taskBackgroundSurfaceBuilder = - createMockSurfaceControlBuilder(taskBackgroundSurface); - mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder); final SurfaceControl captionContainerSurface = mock(SurfaceControl.class); final SurfaceControl.Builder captionContainerSurfaceBuilder = createMockSurfaceControlBuilder(captionContainerSurface); @@ -553,7 +531,7 @@ public class WindowDecorationTests extends ShellTestCase { String name = "Test Window"; WindowDecoration.AdditionalWindow additionalWindow = addWindow(R.layout.desktop_mode_window_decor_handle_menu_app_info_pill, name, - mMockSurfaceControlAddWindowT, x, y, + mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, x, y, width, height, shadowRadius, cornerRadius); return additionalWindow; } diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index 23611efccd73..7e9d44fbdbd1 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -117,12 +117,8 @@ void CacheManager::trimMemory(TrimLevel mode) { // flush and submit all work to the gpu and wait for it to finish mGrContext->flushAndSubmit(/*syncCpu=*/true); - if (!Properties::isHighEndGfx && mode >= TrimLevel::MODERATE) { - mode = TrimLevel::COMPLETE; - } - switch (mode) { - case TrimLevel::COMPLETE: + case TrimLevel::BACKGROUND: mGrContext->freeGpuResources(); SkGraphics::PurgeAllCaches(); mRenderThread.destroyRenderingContext(); diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp index 718d4a16d5c8..96bfc1061d4e 100644 --- a/libs/hwui/renderthread/VulkanManager.cpp +++ b/libs/hwui/renderthread/VulkanManager.cpp @@ -42,6 +42,36 @@ namespace android { namespace uirenderer { namespace renderthread { +static std::array<std::string_view, 18> sEnableExtensions{ + VK_KHR_BIND_MEMORY_2_EXTENSION_NAME, + VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, + VK_KHR_EXTERNAL_MEMORY_CAPABILITIES_EXTENSION_NAME, + VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, + VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, + VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME, + VK_KHR_MAINTENANCE1_EXTENSION_NAME, + VK_KHR_MAINTENANCE2_EXTENSION_NAME, + VK_KHR_MAINTENANCE3_EXTENSION_NAME, + VK_KHR_SAMPLER_YCBCR_CONVERSION_EXTENSION_NAME, + VK_KHR_SURFACE_EXTENSION_NAME, + VK_KHR_SWAPCHAIN_EXTENSION_NAME, + VK_EXT_BLEND_OPERATION_ADVANCED_EXTENSION_NAME, + VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME, + VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME, + VK_EXT_QUEUE_FAMILY_FOREIGN_EXTENSION_NAME, + VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, + VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, +}; + +static bool shouldEnableExtension(const std::string_view& extension) { + for (const auto& it : sEnableExtensions) { + if (it == extension) { + return true; + } + } + return false; +} + static void free_features_extensions_structs(const VkPhysicalDeviceFeatures2& features) { // All Vulkan structs that could be part of the features chain will start with the // structure type followed by the pNext pointer. We cast to the CommonVulkanHeader @@ -139,6 +169,11 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe bool hasKHRSurfaceExtension = false; bool hasKHRAndroidSurfaceExtension = false; for (const VkExtensionProperties& extension : mInstanceExtensionsOwner) { + if (!shouldEnableExtension(extension.extensionName)) { + ALOGV("Not enabling instance extension %s", extension.extensionName); + continue; + } + ALOGV("Enabling instance extension %s", extension.extensionName); mInstanceExtensions.push_back(extension.extensionName); if (!strcmp(extension.extensionName, VK_KHR_SURFACE_EXTENSION_NAME)) { hasKHRSurfaceExtension = true; @@ -219,6 +254,11 @@ void VulkanManager::setupDevice(GrVkExtensions& grExtensions, VkPhysicalDeviceFe LOG_ALWAYS_FATAL_IF(VK_SUCCESS != err); bool hasKHRSwapchainExtension = false; for (const VkExtensionProperties& extension : mDeviceExtensionsOwner) { + if (!shouldEnableExtension(extension.extensionName)) { + ALOGV("Not enabling device extension %s", extension.extensionName); + continue; + } + ALOGV("Enabling device extension %s", extension.extensionName); mDeviceExtensions.push_back(extension.extensionName); if (!strcmp(extension.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME)) { hasKHRSwapchainExtension = true; diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index b1d2e33df3f7..4759689335e9 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -3730,7 +3730,12 @@ public class AudioManager { @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(Manifest.permission.BLUETOOTH_STACK) public void setA2dpSuspended(boolean enable) { - AudioSystem.setParameters("A2dpSuspended=" + enable); + final IAudioService service = getService(); + try { + service.setA2dpSuspended(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** @@ -3743,7 +3748,12 @@ public class AudioManager { @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @RequiresPermission(Manifest.permission.BLUETOOTH_STACK) public void setLeAudioSuspended(boolean enable) { - AudioSystem.setParameters("LeAudioSuspended=" + enable); + final IAudioService service = getService(); + try { + service.setLeAudioSuspended(enable); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } } /** diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java index e73cf87ba9f3..3123ee6dd4d7 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1237,6 +1237,9 @@ public class AudioSystem public static final Set<Integer> DEVICE_IN_ALL_SCO_SET; /** @hide */ public static final Set<Integer> DEVICE_IN_ALL_USB_SET; + /** @hide */ + public static final Set<Integer> DEVICE_IN_ALL_BLE_SET; + static { DEVICE_IN_ALL_SET = new HashSet<>(); DEVICE_IN_ALL_SET.add(DEVICE_IN_COMMUNICATION); @@ -1276,6 +1279,66 @@ public class AudioSystem DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_ACCESSORY); DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_DEVICE); DEVICE_IN_ALL_USB_SET.add(DEVICE_IN_USB_HEADSET); + + DEVICE_IN_ALL_BLE_SET = new HashSet<>(); + DEVICE_IN_ALL_BLE_SET.add(DEVICE_IN_BLE_HEADSET); + } + + /** @hide */ + public static boolean isBluetoothDevice(int deviceType) { + return isBluetoothA2dpOutDevice(deviceType) + || isBluetoothScoDevice(deviceType) + || isBluetoothLeDevice(deviceType); + } + + /** @hide */ + public static boolean isBluetoothOutDevice(int deviceType) { + return isBluetoothA2dpOutDevice(deviceType) + || isBluetoothScoOutDevice(deviceType) + || isBluetoothLeOutDevice(deviceType); + } + + /** @hide */ + public static boolean isBluetoothInDevice(int deviceType) { + return isBluetoothScoInDevice(deviceType) + || isBluetoothLeInDevice(deviceType); + } + + /** @hide */ + public static boolean isBluetoothA2dpOutDevice(int deviceType) { + return DEVICE_OUT_ALL_A2DP_SET.contains(deviceType); + } + + /** @hide */ + public static boolean isBluetoothScoOutDevice(int deviceType) { + return DEVICE_OUT_ALL_SCO_SET.contains(deviceType); + } + + /** @hide */ + public static boolean isBluetoothScoInDevice(int deviceType) { + return DEVICE_IN_ALL_SCO_SET.contains(deviceType); + } + + /** @hide */ + public static boolean isBluetoothScoDevice(int deviceType) { + return isBluetoothScoOutDevice(deviceType) + || isBluetoothScoInDevice(deviceType); + } + + /** @hide */ + public static boolean isBluetoothLeOutDevice(int deviceType) { + return DEVICE_OUT_ALL_BLE_SET.contains(deviceType); + } + + /** @hide */ + public static boolean isBluetoothLeInDevice(int deviceType) { + return DEVICE_IN_ALL_BLE_SET.contains(deviceType); + } + + /** @hide */ + public static boolean isBluetoothLeDevice(int deviceType) { + return isBluetoothLeOutDevice(deviceType) + || isBluetoothLeInDevice(deviceType); } /** @hide */ diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index fe5afc5a717e..7ce189ba85d5 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -231,6 +231,12 @@ interface IAudioService { void setBluetoothScoOn(boolean on); + @EnforcePermission("BLUETOOTH_STACK") + void setA2dpSuspended(boolean on); + + @EnforcePermission("BLUETOOTH_STACK") + void setLeAudioSuspended(boolean enable); + boolean isBluetoothScoOn(); void setBluetoothA2dpOn(boolean on); diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java index 1a3e54d54ee7..afa0a3271906 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java +++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java @@ -391,8 +391,26 @@ public final class SoundTriggerDetector { * @hide */ @Override - public void onError(int status) { - Slog.d(TAG, "onError()" + status); + public void onRecognitionPaused() { + Slog.d(TAG, "onRecognitionPaused()"); + mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE); + } + + /** + * @hide + */ + @Override + public void onRecognitionResumed() { + Slog.d(TAG, "onRecognitionResumed()"); + mHandler.sendEmptyMessage(MSG_DETECTION_RESUME); + } + + /** + * @hide + */ + @Override + public void onPreempted() { + Slog.d(TAG, "onPreempted()"); mHandler.sendEmptyMessage(MSG_DETECTION_ERROR); } @@ -400,18 +418,27 @@ public final class SoundTriggerDetector { * @hide */ @Override - public void onRecognitionPaused() { - Slog.d(TAG, "onRecognitionPaused()"); - mHandler.sendEmptyMessage(MSG_DETECTION_PAUSE); + public void onModuleDied() { + Slog.d(TAG, "onModuleDied()"); + mHandler.sendEmptyMessage(MSG_DETECTION_ERROR); } /** * @hide */ @Override - public void onRecognitionResumed() { - Slog.d(TAG, "onRecognitionResumed()"); - mHandler.sendEmptyMessage(MSG_DETECTION_RESUME); + public void onResumeFailed(int status) { + Slog.d(TAG, "onResumeFailed()" + status); + mHandler.sendEmptyMessage(MSG_DETECTION_ERROR); + } + + /** + * @hide + */ + @Override + public void onPauseFailed(int status) { + Slog.d(TAG, "onPauseFailed()" + status); + mHandler.sendEmptyMessage(MSG_DETECTION_ERROR); } } diff --git a/media/jni/android_media_MediaExtractor.cpp b/media/jni/android_media_MediaExtractor.cpp index 116237f6b998..609c7a40bb36 100644 --- a/media/jni/android_media_MediaExtractor.cpp +++ b/media/jni/android_media_MediaExtractor.cpp @@ -196,6 +196,15 @@ status_t JMediaExtractor::readSampleData( dstSize = (size_t) env->GetDirectBufferCapacity(byteBuf); } + // unlikely, but GetByteArrayElements() can fail + if (dst == nullptr) { + ALOGE("no buffer into which to read the data"); + if (byteArray != NULL) { + env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0); + } + return -ENOMEM; + } + if (dstSize < offset) { if (byteArray != NULL) { env->ReleaseByteArrayElements(byteArray, (jbyte *)dst, 0); @@ -204,8 +213,10 @@ status_t JMediaExtractor::readSampleData( return -ERANGE; } + // passes in the backing memory to use, so it doesn't fail sp<ABuffer> buffer = new ABuffer((char *)dst + offset, dstSize - offset); + buffer->setRange(0, 0); // mark it empty status_t err = mImpl->readSampleData(buffer); if (byteArray != NULL) { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt index e53e956c317c..a9bee039264e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt @@ -107,7 +107,7 @@ class CredentialManagerRepo( initialUiState = when (requestInfo?.type) { RequestInfo.TYPE_CREATE -> { - val defaultProviderId = userConfigRepo.getDefaultProviderId() + val defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId() val isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse() val providerEnableListUiState = getCreateProviderEnableListInitialUiState() val providerDisableListUiState = getCreateProviderDisableListInitialUiState() @@ -115,12 +115,14 @@ class CredentialManagerRepo( getCreateRequestDisplayInfoInitialUiState(originName)!! UiState( createCredentialUiState = CreateFlowUtils.toCreateCredentialUiState( - providerEnableListUiState, - providerDisableListUiState, - defaultProviderId, - requestDisplayInfoUiState, + enabledProviders = providerEnableListUiState, + disabledProviders = providerDisableListUiState, + defaultProviderIdPreferredByApp = + requestDisplayInfoUiState.appPreferredDefaultProviderId, + defaultProviderIdSetByUser = defaultProviderIdSetByUser, + requestDisplayInfo = requestDisplayInfoUiState, isOnPasskeyIntroStateAlready = false, - isPasskeyFirstUse + isPasskeyFirstUse = isPasskeyFirstUse, )!!, getCredentialUiState = null, cancelRequestState = cancelUiRequestState diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt index 24f92c00c772..7581b5c0aa91 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorActivity.kt @@ -19,9 +19,9 @@ package com.android.credentialmanager import android.content.Intent import android.credentials.ui.BaseDialogResult import android.credentials.ui.RequestInfo +import android.net.Uri import android.os.Bundle import android.os.ResultReceiver -import android.provider.Settings import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult @@ -192,7 +192,9 @@ class CredentialSelectorActivity : ComponentActivity() { this@CredentialSelectorActivity.finish() } else if (dialogState == DialogState.CANCELED_FOR_SETTINGS) { Log.d(Constants.LOG_TAG, "Received signal to finish the activity and launch settings.") - this@CredentialSelectorActivity.startActivity(Intent(Settings.ACTION_SYNC_SETTINGS)) + val settingsIntent = Intent(ACTION_CREDENTIAL_PROVIDER) + settingsIntent.data = Uri.parse("package:" + this.getPackageName()) + this@CredentialSelectorActivity.startActivity(settingsIntent) this@CredentialSelectorActivity.finish() } } @@ -222,4 +224,8 @@ class CredentialSelectorActivity : ComponentActivity() { dismissOnTimeout = true, ) } + + companion object { + const val ACTION_CREDENTIAL_PROVIDER = "android.settings.CREDENTIAL_PROVIDER" + } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt index 29ec970966d6..4d2bb4c6016a 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -250,9 +250,15 @@ class CredentialSelectorViewModel( return } val newUiState = CreateFlowUtils.toCreateCredentialUiState( - prevUiState.enabledProviders, prevUiState.disabledProviders, - userConfigRepo.getDefaultProviderId(), prevUiState.requestDisplayInfo, true, - userConfigRepo.getIsPasskeyFirstUse()) + enabledProviders = prevUiState.enabledProviders, + disabledProviders = prevUiState.disabledProviders, + defaultProviderIdPreferredByApp = + prevUiState.requestDisplayInfo.appPreferredDefaultProviderId, + defaultProviderIdSetByUser = userConfigRepo.getDefaultProviderId(), + requestDisplayInfo = prevUiState.requestDisplayInfo, + isOnPasskeyIntroStateAlready = true, + isPasskeyFirstUse = userConfigRepo.getIsPasskeyFirstUse() + ) if (newUiState == null) { Log.d(Constants.LOG_TAG, "Unable to update create ui state") onInternalError() diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt index 64addf237f9b..108f4945c9e9 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -198,7 +198,8 @@ class GetFlowUtils { it.type, it.credentialRetrievalData, it.credentialRetrievalData, - it.isSystemProviderRequired + it.isSystemProviderRequired, + it.allowedProviders, ) if (credentialOptionJetpack is GetPublicKeyCredentialOption) { credentialOptionJetpack.preferImmediatelyAvailableCredentials @@ -462,8 +463,12 @@ class CreateFlowUtils { createCredentialRequest.type, createCredentialRequest.credentialData, createCredentialRequest.candidateQueryData, - createCredentialRequest.isSystemProviderRequired + createCredentialRequest.isSystemProviderRequired, + createCredentialRequest.origin, ) + val appPreferredDefaultProviderId: String? = + if (!requestInfo.hasPermissionToOverrideDefault()) null + else createCredentialRequestJetpack?.displayInfo?.preferDefaultProvider return when (createCredentialRequestJetpack) { is CreatePasswordRequest -> RequestDisplayInfo( createCredentialRequestJetpack.id, @@ -472,6 +477,7 @@ class CreateFlowUtils { appLabel, context.getDrawable(R.drawable.ic_password_24) ?: return null, preferImmediatelyAvailableCredentials = false, + appPreferredDefaultProviderId = appPreferredDefaultProviderId, ) is CreatePublicKeyCredentialRequest -> { newRequestDisplayInfoFromPasskeyJson( @@ -480,6 +486,7 @@ class CreateFlowUtils { context = context, preferImmediatelyAvailableCredentials = createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, + appPreferredDefaultProviderId = appPreferredDefaultProviderId, ) } is CreateCustomCredentialRequest -> { @@ -495,6 +502,7 @@ class CreateFlowUtils { typeIcon = displayInfo.credentialTypeIcon?.loadDrawable(context) ?: context.getDrawable(R.drawable.ic_other_sign_in_24) ?: return null, preferImmediatelyAvailableCredentials = false, + appPreferredDefaultProviderId = appPreferredDefaultProviderId, ) } else -> null @@ -504,20 +512,27 @@ class CreateFlowUtils { fun toCreateCredentialUiState( enabledProviders: List<EnabledProviderInfo>, disabledProviders: List<DisabledProviderInfo>?, - defaultProviderId: String?, + defaultProviderIdPreferredByApp: String?, + defaultProviderIdSetByUser: String?, requestDisplayInfo: RequestDisplayInfo, isOnPasskeyIntroStateAlready: Boolean, isPasskeyFirstUse: Boolean, ): CreateCredentialUiState? { var lastSeenProviderWithNonEmptyCreateOptions: EnabledProviderInfo? = null var remoteEntry: RemoteInfo? = null - var defaultProvider: EnabledProviderInfo? = null + var defaultProviderPreferredByApp: EnabledProviderInfo? = null + var defaultProviderSetByUser: EnabledProviderInfo? = null var createOptionsPairs: MutableList<Pair<CreateOptionInfo, EnabledProviderInfo>> = mutableListOf() enabledProviders.forEach { enabledProvider -> - if (defaultProviderId != null) { - if (enabledProvider.id == defaultProviderId) { - defaultProvider = enabledProvider + if (defaultProviderIdPreferredByApp != null) { + if (enabledProvider.id == defaultProviderIdPreferredByApp) { + defaultProviderPreferredByApp = enabledProvider + } + } + if (defaultProviderIdSetByUser != null) { + if (enabledProvider.id == defaultProviderIdSetByUser) { + defaultProviderSetByUser = enabledProvider } } if (enabledProvider.createOptions.isNotEmpty()) { @@ -536,12 +551,14 @@ class CreateFlowUtils { remoteEntry = currRemoteEntry } } + val defaultProvider = defaultProviderPreferredByApp ?: defaultProviderSetByUser val initialScreenState = toCreateScreenState( - /*createOptionSize=*/createOptionsPairs.size, - /*isOnPasskeyIntroStateAlready=*/isOnPasskeyIntroStateAlready, - /*requestDisplayInfo=*/requestDisplayInfo, - /*defaultProvider=*/defaultProvider, /*remoteEntry=*/remoteEntry, - /*isPasskeyFirstUse=*/isPasskeyFirstUse + createOptionSize = createOptionsPairs.size, + isOnPasskeyIntroStateAlready = isOnPasskeyIntroStateAlready, + requestDisplayInfo = requestDisplayInfo, + defaultProvider = defaultProvider, + remoteEntry = remoteEntry, + isPasskeyFirstUse = isPasskeyFirstUse ) ?: return null return CreateCredentialUiState( enabledProviders = enabledProviders, @@ -667,6 +684,7 @@ class CreateFlowUtils { appLabel: String, context: Context, preferImmediatelyAvailableCredentials: Boolean, + appPreferredDefaultProviderId: String?, ): RequestDisplayInfo? { val json = JSONObject(requestJson) var passkeyUsername = "" @@ -687,6 +705,7 @@ class CreateFlowUtils { appLabel, context.getDrawable(R.drawable.ic_passkey_24) ?: return null, preferImmediatelyAvailableCredentials, + appPreferredDefaultProviderId, ) } } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt index 4332fb34ce79..12bb6298b282 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt @@ -107,6 +107,7 @@ data class RequestDisplayInfo( val appName: String, val typeIcon: Drawable, val preferImmediatelyAvailableCredentials: Boolean, + val appPreferredDefaultProviderId: String?, ) /** diff --git a/packages/DynamicSystemInstallationService/AndroidManifest.xml b/packages/DynamicSystemInstallationService/AndroidManifest.xml index c2aaeace1af6..776bf2b761ce 100644 --- a/packages/DynamicSystemInstallationService/AndroidManifest.xml +++ b/packages/DynamicSystemInstallationService/AndroidManifest.xml @@ -36,6 +36,10 @@ <data android:scheme="http" /> <data android:scheme="https" /> </intent-filter> + <intent-filter> + <action android:name="android.os.image.action.START_INSTALL" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> </activity> <receiver diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java index 2c4b4786c968..b265a425d2e7 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java @@ -19,6 +19,7 @@ package com.android.dynsystem; import static android.os.AsyncTask.Status.FINISHED; import static android.os.AsyncTask.Status.PENDING; import static android.os.AsyncTask.Status.RUNNING; +import static android.os.image.DynamicSystemClient.ACTION_HIDE_NOTIFICATION; import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_IF_IN_USE; import static android.os.image.DynamicSystemClient.ACTION_START_INSTALL; import static android.os.image.DynamicSystemClient.CAUSE_ERROR_EXCEPTION; @@ -27,6 +28,8 @@ import static android.os.image.DynamicSystemClient.CAUSE_ERROR_IO; import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_CANCELLED; import static android.os.image.DynamicSystemClient.CAUSE_INSTALL_COMPLETED; import static android.os.image.DynamicSystemClient.CAUSE_NOT_SPECIFIED; +import static android.os.image.DynamicSystemClient.KEY_ENABLE_WHEN_COMPLETED; +import static android.os.image.DynamicSystemClient.KEY_ONE_SHOT; import static android.os.image.DynamicSystemClient.STATUS_IN_PROGRESS; import static android.os.image.DynamicSystemClient.STATUS_IN_USE; import static android.os.image.DynamicSystemClient.STATUS_NOT_STARTED; @@ -77,8 +80,6 @@ public class DynamicSystemInstallationService extends Service private static final String TAG = "DynamicSystemInstallationService"; - // TODO (b/131866826): This is currently for test only. Will move this to System API. - static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED"; static final String KEY_DSU_SLOT = "KEY_DSU_SLOT"; static final String DEFAULT_DSU_SLOT = "dsu"; static final String KEY_PUBKEY = "KEY_PUBKEY"; @@ -172,6 +173,8 @@ public class DynamicSystemInstallationService extends Service // This is for testing only now private boolean mEnableWhenCompleted; + private boolean mOneShot; + private boolean mHideNotification; private InstallationAsyncTask.Progress mInstallTaskProgress; private InstallationAsyncTask mInstallTask; @@ -229,6 +232,8 @@ public class DynamicSystemInstallationService extends Service executeRebootToNormalCommand(); } else if (ACTION_NOTIFY_IF_IN_USE.equals(action)) { executeNotifyIfInUseCommand(); + } else if (ACTION_HIDE_NOTIFICATION.equals(action)) { + executeHideNotificationCommand(); } return Service.START_NOT_STICKY; @@ -318,6 +323,7 @@ public class DynamicSystemInstallationService extends Service long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0); long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0); mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false); + mOneShot = intent.getBooleanExtra(KEY_ONE_SHOT, true); String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT); String publicKey = intent.getStringExtra(KEY_PUBKEY); @@ -384,9 +390,9 @@ public class DynamicSystemInstallationService extends Service boolean enabled = false; if (mInstallTask != null && mInstallTask.isCompleted()) { - enabled = mInstallTask.commit(); + enabled = mInstallTask.commit(mOneShot); } else if (isDynamicSystemInstalled()) { - enabled = mDynSystem.setEnable(true, true); + enabled = mDynSystem.setEnable(true, mOneShot); } else { Log.e(TAG, "Trying to reboot to AOT while there is no complete installation"); return; @@ -439,12 +445,16 @@ public class DynamicSystemInstallationService extends Service private void executeNotifyIfInUseCommand() { switch (getStatus()) { case STATUS_IN_USE: - startForeground(NOTIFICATION_ID, - buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED)); + if (!mHideNotification) { + startForeground(NOTIFICATION_ID, + buildNotification(STATUS_IN_USE, CAUSE_NOT_SPECIFIED)); + } break; case STATUS_READY: - startForeground(NOTIFICATION_ID, - buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED)); + if (!mHideNotification) { + startForeground(NOTIFICATION_ID, + buildNotification(STATUS_READY, CAUSE_NOT_SPECIFIED)); + } break; case STATUS_IN_PROGRESS: break; @@ -454,6 +464,16 @@ public class DynamicSystemInstallationService extends Service } } + private void executeHideNotificationCommand() { + mHideNotification = true; + switch (getStatus()) { + case STATUS_IN_USE: + case STATUS_READY: + stopForeground(STOP_FOREGROUND_REMOVE); + break; + } + } + private void resetTaskAndStop() { resetTaskAndStop(/* removeNotification= */ false); } diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java index a41399fb0d0d..42b620abe734 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java @@ -803,7 +803,7 @@ class InstallationAsyncTask extends AsyncTask<String, Long, Throwable> { return mIsCompleted; } - boolean commit() { - return mDynSystem.setEnable(true, true); + boolean commit(boolean oneShot) { + return mDynSystem.setEnable(true, oneShot); } } diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java index 64e42cc595ec..b52272961e4b 100644 --- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java +++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java @@ -16,6 +16,8 @@ package com.android.dynsystem; +import static android.os.image.DynamicSystemClient.KEY_KEYGUARD_USE_DEFAULT_STRINGS; + import android.app.Activity; import android.app.KeyguardManager; import android.content.Context; @@ -47,10 +49,7 @@ public class VerificationActivity extends Activity { KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); if (km != null) { - String title = getString(R.string.keyguard_title); - String description = getString(R.string.keyguard_description); - Intent intent = km.createConfirmDeviceCredentialIntent(title, description); - + Intent intent = createConfirmDeviceCredentialIntent(km); if (intent == null) { Log.d(TAG, "This device is not protected by a password/pin"); startInstallationService(); @@ -63,6 +62,23 @@ public class VerificationActivity extends Activity { } } + private Intent createConfirmDeviceCredentialIntent(KeyguardManager km) { + final boolean useDefaultStrings = + getIntent().getBooleanExtra(KEY_KEYGUARD_USE_DEFAULT_STRINGS, false); + final String title; + final String description; + if (useDefaultStrings) { + // Use default strings provided by keyguard manager + title = null; + description = null; + } else { + // Use custom strings provided by DSU + title = getString(R.string.keyguard_title); + description = getString(R.string.keyguard_description); + } + return km.createConfirmDeviceCredentialIntent(title, description); + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) { diff --git a/packages/PrintSpooler/tests/outofprocess/Android.bp b/packages/PrintSpooler/tests/outofprocess/Android.bp index 69a1d7fa59e4..ef0d122c7273 100644 --- a/packages/PrintSpooler/tests/outofprocess/Android.bp +++ b/packages/PrintSpooler/tests/outofprocess/Android.bp @@ -31,7 +31,7 @@ android_test { libs: ["android.test.runner.stubs"], static_libs: [ "androidx.test.rules", - "ub-uiautomator", + "androidx.test.uiautomator_uiautomator", "mockito-target-minus-junit4", "print-test-util-lib", ], diff --git a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java index 132545b2d5d2..1509b7077046 100644 --- a/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java +++ b/packages/PrintSpooler/tests/outofprocess/src/com/android/printspooler/outofprocess/tests/WorkflowTest.java @@ -35,15 +35,15 @@ import android.print.test.services.AddPrintersActivity; import android.print.test.services.FirstPrintService; import android.print.test.services.PrinterDiscoverySessionCallbacks; import android.print.test.services.StubbablePrinterDiscoverySession; -import android.support.test.uiautomator.By; -import android.support.test.uiautomator.UiDevice; -import android.support.test.uiautomator.UiObject; -import android.support.test.uiautomator.UiObjectNotFoundException; -import android.support.test.uiautomator.UiSelector; -import android.support.test.uiautomator.Until; import android.util.Log; import androidx.test.filters.LargeTest; +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject; +import androidx.test.uiautomator.UiObjectNotFoundException; +import androidx.test.uiautomator.UiSelector; +import androidx.test.uiautomator.Until; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java index 8cda37665035..bf24c86b8d8b 100644 --- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java +++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java @@ -59,32 +59,36 @@ public class FooterPreference extends Preference { public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); TextView title = holder.itemView.findViewById(android.R.id.title); - if (!TextUtils.isEmpty(mContentDescription)) { + if (title != null && !TextUtils.isEmpty(mContentDescription)) { title.setContentDescription(mContentDescription); } TextView learnMore = holder.itemView.findViewById(R.id.settingslib_learn_more); - if (learnMore != null && mLearnMoreListener != null) { - learnMore.setVisibility(View.VISIBLE); - if (TextUtils.isEmpty(mLearnMoreText)) { - mLearnMoreText = learnMore.getText(); + if (learnMore != null) { + if (mLearnMoreListener != null) { + learnMore.setVisibility(View.VISIBLE); + if (TextUtils.isEmpty(mLearnMoreText)) { + mLearnMoreText = learnMore.getText(); + } else { + learnMore.setText(mLearnMoreText); + } + SpannableString learnMoreText = new SpannableString(mLearnMoreText); + if (mLearnMoreSpan != null) { + learnMoreText.removeSpan(mLearnMoreSpan); + } + mLearnMoreSpan = new FooterLearnMoreSpan(mLearnMoreListener); + learnMoreText.setSpan(mLearnMoreSpan, 0, + learnMoreText.length(), 0); + learnMore.setText(learnMoreText); } else { - learnMore.setText(mLearnMoreText); + learnMore.setVisibility(View.GONE); } - SpannableString learnMoreText = new SpannableString(mLearnMoreText); - if (mLearnMoreSpan != null) { - learnMoreText.removeSpan(mLearnMoreSpan); - } - mLearnMoreSpan = new FooterLearnMoreSpan(mLearnMoreListener); - learnMoreText.setSpan(mLearnMoreSpan, 0, - learnMoreText.length(), 0); - learnMore.setText(learnMoreText); - } else { - learnMore.setVisibility(View.GONE); } View icon = holder.itemView.findViewById(R.id.icon_frame); - icon.setVisibility(mIconVisibility); + if (icon != null) { + icon.setVisibility(mIconVisibility); + } } @Override diff --git a/packages/SettingsLib/OWNERS b/packages/SettingsLib/OWNERS index 1b3020af3d78..81340f561bc1 100644 --- a/packages/SettingsLib/OWNERS +++ b/packages/SettingsLib/OWNERS @@ -3,9 +3,8 @@ dsandler@android.com edgarwang@google.com evanlaird@google.com juliacr@google.com -lijun@google.com -songchenxi@google.com yantingyang@google.com +ykhung@google.com # Exempt resource files (because they are in a flat directory and too hard to manage via OWNERS) per-file *.xml=* diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt index 30a4349e942f..6f2c38caa3bc 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTab.kt @@ -18,11 +18,11 @@ package com.android.settingslib.spa.widget.scaffold import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Tab import androidx.compose.material3.Text +import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -46,7 +46,7 @@ internal fun SettingsTab( selected = selected, onClick = onClick, modifier = Modifier - .height(48.dp) + .minimumInteractiveComponentSize() .padding(horizontal = 4.dp, vertical = 6.dp) .clip(SettingsShape.CornerMedium) .background( diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index ff80f52218ad..214c9035fc8e 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1262,6 +1262,12 @@ <!-- Button label for generic cancel action [CHAR LIMIT=20] --> <string name="cancel">Cancel</string> + <!-- Button label for generic next action [CHAR LIMIT=20] --> + <string name="next">Next</string> + <!-- Button label for generic back action [CHAR LIMIT=20] --> + <string name="back">Back</string> + <!-- Button label for generic save action [CHAR LIMIT=20] --> + <string name="save">Save</string> <!-- Button label for generic OK action [CHAR LIMIT=20] --> <string name="okay">OK</string> <!-- Button label for generic Done action, to be pressed when an action has been completed [CHAR LIMIT=20] --> @@ -1388,9 +1394,11 @@ <!-- Message for add user confirmation dialog - short version. [CHAR LIMIT=none] --> <string name="user_add_user_message_short">When you add a new user, that person needs to set up their space.\n\nAny user can update apps for all other users. </string> <!-- Title for grant user admin privileges dialog [CHAR LIMIT=65] --> - <string name="user_grant_admin_title">Give this user admin privileges?</string> + <string name="user_grant_admin_title">Make this user an admin?</string> <!-- Message for grant admin privileges dialog. [CHAR LIMIT=none] --> - <string name="user_grant_admin_message">As an admin, they will be able to manage other users, modify device settings and factory reset the device.</string> + <string name="user_grant_admin_message">Admins have special privileges that other users don\’t. An admin can manage all users, update or reset this device, modify settings, see all installed apps, and grant or revoke admin privileges for others.</string> + <!-- Confirmation button for grant user admin privileges dialog [CHAR LIMIT=65] --> + <string name="user_grant_admin_button">Make admin</string> <!-- Title of dialog to setup a new user [CHAR LIMIT=30] --> <string name="user_setup_dialog_title">Set up user now?</string> <!-- Message in dialog to setup a new user after creation [CHAR LIMIT=none] --> @@ -1458,9 +1466,9 @@ <string name="guest_exit_dialog_message">This will delete apps and data from the current guest session</string> <!-- Dialog message on action grant admin privileges [CHAR LIMIT=60] --> - <string name="grant_admin">Give this user admin privileges</string> + <string name="grant_admin">Yes, make them an admin</string> <!-- Dialog message on action not grant admin privileges [CHAR LIMIT=60] --> - <string name="not_grant_admin">Do not give user admin privileges</string> + <string name="not_grant_admin">No, don\’t make them an admin</string> <!-- Dialog button on action exit guest (ephemeral guest) [CHAR LIMIT=80] --> <string name="guest_exit_dialog_button">Exit</string> <!-- Dialog title on action exit guest (non-ephemeral guest) [CHAR LIMIT=32] --> diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index e22f3f0e720d..5fbb4c3e5712 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -37,8 +37,6 @@ import com.android.settingslib.Utils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; @@ -287,16 +285,7 @@ public class HearingAidProfile implements LocalBluetoothProfile { return defaultValue; } - try { - Method method = mService.getClass().getDeclaredMethod("getDeviceSideInternal", - BluetoothDevice.class); - method.setAccessible(true); - return (int) method.invoke(mService, device); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - Log.e(TAG, "fail to get getDeviceSideInternal\n" + e.toString() + "\n" - + Log.getStackTraceString(new Throwable())); - return defaultValue; - } + return mService.getDeviceSide(device); } /** @@ -313,17 +302,7 @@ public class HearingAidProfile implements LocalBluetoothProfile { return defaultValue; } - try { - Method method = mService.getClass().getDeclaredMethod("getDeviceModeInternal", - BluetoothDevice.class); - method.setAccessible(true); - return (int) method.invoke(mService, device); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - Log.e(TAG, "fail to get getDeviceModeInternal\n" + e.toString() + "\n" - + Log.getStackTraceString(new Throwable())); - - return defaultValue; - } + return mService.getDeviceMode(device); } public String toString() { diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java index 55125c53b4c9..049c90e971a9 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java @@ -18,6 +18,9 @@ package com.android.settingslib.widget; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + import android.content.Context; import android.view.LayoutInflater; import android.view.View; @@ -87,4 +90,52 @@ public class FooterPreferenceTest { assertThat(mFooterPreference.mIconVisibility).isEqualTo(View.GONE); } + + @Test + public void onBindViewHolder_whenTitleIsNull_shouldNotRaiseNpe() { + PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests( + LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null))); + when(viewHolder.findViewById(R.id.title)).thenReturn(null); + + Throwable actualThrowable = null; + try { + mFooterPreference.onBindViewHolder(viewHolder); + } catch (Throwable throwable) { + actualThrowable = throwable; + } + + assertThat(actualThrowable).isNull(); + } + + @Test + public void onBindViewHolder_whenLearnMoreIsNull_shouldNotRaiseNpe() { + PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests( + LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null))); + when(viewHolder.findViewById(R.id.settingslib_learn_more)).thenReturn(null); + + Throwable actualThrowable = null; + try { + mFooterPreference.onBindViewHolder(viewHolder); + } catch (Throwable throwable) { + actualThrowable = throwable; + } + + assertThat(actualThrowable).isNull(); + } + + @Test + public void onBindViewHolder_whenIconFrameIsNull_shouldNotRaiseNpe() { + PreferenceViewHolder viewHolder = spy(PreferenceViewHolder.createInstanceForTests( + LayoutInflater.from(mContext).inflate(R.layout.preference_footer, null))); + when(viewHolder.findViewById(R.id.icon_frame)).thenReturn(null); + + Throwable actualThrowable = null; + try { + mFooterPreference.onBindViewHolder(viewHolder); + } catch (Throwable throwable) { + actualThrowable = throwable; + } + + assertThat(actualThrowable).isNull(); + } } diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 7a1d9a3ad025..9d32e905d85d 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -259,6 +259,24 @@ filegroup { "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt", "tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt", "tests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt", + + // Biometric + "tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt", + "tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt", + "tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt", + "tests/src/com/android/systemui/biometrics/AuthControllerTest.java", + "tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java", + "tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt", + "tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt", + "tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt", + "tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java", + "tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java", + "tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java", + "tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java", + "tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java", + "tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt", + "tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt", + "tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt", ], path: "tests/src", } @@ -403,6 +421,10 @@ android_app { privileged: true, resource_dirs: [], kotlincflags: ["-Xjvm-default=all"], + optimize: { + shrink_resources: false, + proguard_flags_files: ["proguard.flags"], + }, plugins: ["dagger2-compiler"], } diff --git a/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt b/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt index de49d1c2c5ee..4734a3887f78 100644 --- a/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt +++ b/packages/SystemUI/common/src/com/android/systemui/common/buffer/RingBuffer.kt @@ -19,14 +19,16 @@ package com.android.systemui.common.buffer import kotlin.math.max /** - * A simple ring buffer implementation + * A simple ring buffer of recycled items * - * Use [advance] to get the least recent item in the buffer (and then presumably fill it with - * appropriate data). This will cause it to become the most recent item. + * Use [advance] to add items to the buffer. * * As the buffer is used, it will grow, allocating new instances of T using [factory] until it - * reaches [maxSize]. After this point, no new instances will be created. Instead, the "oldest" - * instances will be recycled from the back of the buffer and placed at the front. + * reaches [maxSize]. After this point, no new instances will be created. Instead, calls to + * [advance] will recycle the "oldest" instance from the start of the buffer, placing it at the end. + * + * The items in the buffer are "recycled" in that they are reused, but it is up to the caller of + * [advance] to properly reset any data that was previously stored on those items. * * @param maxSize The maximum size the buffer can grow to before it begins functioning as a ring. * @param factory A function that creates a fresh instance of T. Used by the buffer while it's @@ -37,11 +39,13 @@ class RingBuffer<T>(private val maxSize: Int, private val factory: () -> T) : It private val buffer = MutableList<T?>(maxSize) { null } /** - * An abstract representation that points to the "end" of the buffer. Increments every time - * [advance] is called and never wraps. Use [indexOf] to calculate the associated index into the - * backing array. Always points to the "next" available slot in the buffer. Before the buffer - * has completely filled, the value pointed to will be null. Afterward, it will be the value at - * the "beginning" of the buffer. + * An abstract representation that points to the "end" of the buffer, i.e. one beyond the + * location of the last item. Increments every time [advance] is called and is never wrapped. + * + * Use [indexOf] to calculate the associated index into the backing array. Before the buffer has + * been completely filled, this will point to the next empty slot to fill; afterwards it will + * point to the next item that should be recycled (which, because the buffer is a ring, is the + * "start" of the buffer). * * This value is unlikely to overflow. Assuming [advance] is called at rate of 100 calls/ms, * omega will overflow after a little under three million years of continuous operation. @@ -56,12 +60,15 @@ class RingBuffer<T>(private val maxSize: Int, private val factory: () -> T) : It get() = if (omega < maxSize) omega.toInt() else maxSize /** - * Advances the buffer's position by one and returns the value that is now present at the "end" - * of the buffer. If the buffer is not yet full, uses [factory] to create a new item. Otherwise, - * reuses the value that was previously at the "beginning" of the buffer. + * Adds an item to the end of the buffer. The caller should reset the returned item's contents + * and then fill it with appropriate data. + * + * If the buffer is not yet full, uses [factory] to create a new item. Otherwise, it recycles + * the oldest item from the front of the buffer and moves it to the end. * - * IMPORTANT: The value is returned as-is, without being reset. It will retain any data that was - * previously stored on it. + * Importantly, recycled items are returned as-is, without being reset. They will retain any + * data that was previously stored on them. Callers must make sure to clear any historical data, + * if necessary. */ fun advance(): T { val index = indexOf(omega) @@ -72,8 +79,7 @@ class RingBuffer<T>(private val maxSize: Int, private val factory: () -> T) : It /** * Returns the value stored at [index], which can range from 0 (the "start", or oldest element - * of the buffer) to [size] - * - 1 (the "end", or newest element of the buffer). + * of the buffer) to [size] - 1 (the "end", or newest element of the buffer). */ operator fun get(index: Int): T { if (index < 0 || index >= size) { @@ -89,12 +95,6 @@ class RingBuffer<T>(private val maxSize: Int, private val factory: () -> T) : It return buffer[indexOf(start + index)]!! } - inline fun forEach(action: (T) -> Unit) { - for (i in 0 until size) { - action(get(i)) - } - } - override fun iterator(): Iterator<T> { return object : Iterator<T> { private var position: Int = 0 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 a7e95b58a6e4..eaf3229bf0c8 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.io.PrintWriter import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicBoolean import kotlinx.coroutines.CoroutineDispatcher @@ -485,6 +486,14 @@ open class ClockRegistry( return availableClocks[targetClockId]?.provider?.createClock(settings) } + fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("ClockRegistry:") + pw.println(" settings = $settings") + for ((id, info) in availableClocks) { + pw.println(" availableClocks[$id] = $info") + } + } + private data class ClockInfo( val metadata: ClockMetadata, var provider: ClockProvider?, diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java index 1fec3314a13e..6d4dbf632d76 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java @@ -79,6 +79,11 @@ public interface ActivityStarter { void postStartActivityDismissingKeyguard(Intent intent, int delay); void postStartActivityDismissingKeyguard(Intent intent, int delay, @Nullable ActivityLaunchAnimator.Controller animationController); + + /** Posts a start activity intent that dismisses keyguard. */ + void postStartActivityDismissingKeyguard(Intent intent, int delay, + @Nullable ActivityLaunchAnimator.Controller animationController, + @Nullable String customMessage); void postStartActivityDismissingKeyguard(PendingIntent intent); /** @@ -93,6 +98,10 @@ public interface ActivityStarter { void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel, boolean afterKeyguardGone); + /** Authenticates if needed and dismisses keyguard to execute an action. */ + void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel, + boolean afterKeyguardGone, @Nullable String customMessage); + interface Callback { void onActivityStarted(int resultCode); } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt index f71c137363c5..2dd146c5134d 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/WeatherData.kt @@ -47,6 +47,7 @@ constructor( } } + // Values for WeatherStateIcon must stay in sync with go/g3-WeatherStateIcon enum class WeatherStateIcon(val id: Int) { UNKNOWN_ICON(0), diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt index 4a6e0b61ecc9..0a7ccc525a73 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/log/LogBuffer.kt @@ -253,8 +253,8 @@ constructor( @Synchronized fun unfreeze() { if (frozen) { - log(TAG, LogLevel.DEBUG, { str1 = name }, { "$str1 unfrozen" }) frozen = false + log(TAG, LogLevel.DEBUG, { str1 = name }, { "$str1 unfrozen" }) } } diff --git a/packages/SystemUI/res/layout/controls_more_item.xml b/packages/SystemUI/res/layout/controls_more_item.xml index da9c43ccc1e9..73d1c54b1c64 100644 --- a/packages/SystemUI/res/layout/controls_more_item.xml +++ b/packages/SystemUI/res/layout/controls_more_item.xml @@ -13,13 +13,17 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<TextView - xmlns:android="http://schemas.android.com/apk/res/android" +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/controls_more_item_text" style="@style/Control.MenuItem" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="start" + android:layout_height="@dimen/control_menu_item_height" + android:layout_gravity="center_vertical" + android:background="@drawable/controls_popup_item_background" android:paddingStart="@dimen/control_menu_horizontal_padding" android:paddingEnd="@dimen/control_menu_horizontal_padding" - android:textDirection="locale"/> - + android:textDirection="locale" + android:textSize="@dimen/control_item_text_size" + tools:fontFamily="@null" + tools:text="@tools:sample/lorem/random" />
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/controls_spinner_item.xml b/packages/SystemUI/res/layout/controls_spinner_item.xml index 4048d0371aa8..811965121702 100644 --- a/packages/SystemUI/res/layout/controls_spinner_item.xml +++ b/packages/SystemUI/res/layout/controls_spinner_item.xml @@ -16,18 +16,18 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="@dimen/control_popup_item_height" + android:layout_height="@dimen/control_apps_popup_item_height" android:background="@drawable/controls_popup_item_background" android:gravity="center_vertical|start" android:orientation="horizontal" - android:paddingStart="@dimen/control_popup_item_padding" - android:paddingEnd="@dimen/control_popup_item_padding"> + android:paddingStart="@dimen/control_menu_horizontal_padding" + android:paddingEnd="@dimen/control_menu_horizontal_padding"> <ImageView android:id="@+id/app_icon" android:layout_width="@dimen/controls_header_app_icon_size" android:layout_height="@dimen/controls_header_app_icon_size" - android:layout_marginEnd="@dimen/control_popup_item_padding" + android:layout_marginEnd="@dimen/control_menu_horizontal_padding" android:contentDescription="@null" tools:src="@drawable/ic_android" /> diff --git a/packages/SystemUI/res/layout/notification_snooze.xml b/packages/SystemUI/res/layout/notification_snooze.xml index 11ec02575e97..8b5368098c00 100644 --- a/packages/SystemUI/res/layout/notification_snooze.xml +++ b/packages/SystemUI/res/layout/notification_snooze.xml @@ -21,6 +21,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" + android:paddingTop="2dp" + android:paddingBottom="2dp" android:background="?androidprv:attr/colorSurface" android:theme="@style/Theme.SystemUI"> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 0eb0a078a478..9cb8aa0142bf 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1199,19 +1199,17 @@ <dimen name="controls_top_margin">48dp</dimen> <dimen name="controls_content_margin_horizontal">0dp</dimen> <dimen name="control_header_text_size">24sp</dimen> - <dimen name="control_item_text_size">16sp</dimen> + <dimen name="control_item_text_size">14sp</dimen> <dimen name="control_menu_item_text_size">16sp</dimen> - <dimen name="control_menu_item_min_height">56dp</dimen> + <dimen name="control_menu_item_height">54dp</dimen> <dimen name="control_menu_vertical_padding">12dp</dimen> - <dimen name="control_menu_horizontal_padding">16dp</dimen> - <dimen name="control_popup_item_corner_radius">4dp</dimen> - <dimen name="control_popup_item_height">56dp</dimen> - <dimen name="control_popup_item_padding">16dp</dimen> - <dimen name="control_popup_items_divider_height">1dp</dimen> + <dimen name="control_menu_horizontal_padding">@dimen/notification_side_paddings</dimen> + <dimen name="control_apps_popup_item_height">56dp</dimen> + <dimen name="control_popup_item_corner_radius">@dimen/notification_corner_radius_small</dimen> + <dimen name="control_popup_items_divider_height">@dimen/controls_app_divider_height</dimen> <dimen name="control_popup_max_width">380dp</dimen> - <dimen name="control_popup_corner_radius">28dp</dimen> + <dimen name="control_popup_corner_radius">@dimen/notification_corner_radius</dimen> <dimen name="control_popup_horizontal_margin">16dp</dimen> - <dimen name="control_spinner_padding_vertical">24dp</dimen> <dimen name="control_spinner_padding_horizontal">20dp</dimen> <dimen name="control_text_size">14sp</dimen> <dimen name="control_icon_size">24dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 74ae954a539c..26502f1c1752 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -3069,6 +3069,9 @@ --> <string name="lock_screen_settings">Customize lock screen</string> + <!-- Title of security view when we want to authenticate before customizing the lockscreen. [CHAR LIMIT=NONE] --> + <string name="keyguard_unlock_to_customize_ls">Unlock to customize lock screen</string> + <!-- Content description for Wi-Fi not available icon on dream [CHAR LIMIT=NONE]--> <string name="wifi_unavailable_dream_overlay_content_description">Wi-Fi not available</string> @@ -3086,4 +3089,7 @@ <!-- Content description for when assistant attention is active [CHAR LIMIT=NONE] --> <string name="assistant_attention_content_description">Assistant attention on</string> + + <!--- Content of toast triggered when the notes app entry point is triggered without setting a default notes app. [CHAR LIMIT=NONE] --> + <string name="set_default_notes_app_toast_content">Set default notes app in Settings</string> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index a359d3b21938..9d0cc11c93c2 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -895,7 +895,7 @@ <item name="android:textColor">@color/control_primary_text</item> <item name="android:singleLine">true</item> <item name="android:gravity">center_vertical</item> - <item name="android:minHeight">@dimen/control_menu_item_min_height</item> + <item name="android:minHeight">@dimen/control_menu_item_height</item> </style> <style name="Control.Spinner"> diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java index 1980f70d63a4..510fcbfd8bee 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardAbsKeyInputViewController.java @@ -116,7 +116,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey } @Override - public void showMessage(CharSequence message, ColorStateList colorState) { + public void showMessage(CharSequence message, ColorStateList colorState, boolean animated) { if (mMessageAreaController == null) { return; } @@ -124,7 +124,7 @@ public abstract class KeyguardAbsKeyInputViewController<T extends KeyguardAbsKey if (colorState != null) { mMessageAreaController.setNextMessageColor(colorState); } - mMessageAreaController.setMessage(message); + mMessageAreaController.setMessage(message, animated); } // Allow subclasses to override this behavior diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java index 0326b6d3edca..5ba0ad62e9fb 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java @@ -294,11 +294,11 @@ public class KeyguardClockSwitch extends RelativeLayout { public void dump(PrintWriter pw, String[] args) { pw.println("KeyguardClockSwitch:"); - pw.println(" mSmallClockFrame: " + mSmallClockFrame); - pw.println(" mSmallClockFrame.alpha: " + mSmallClockFrame.getAlpha()); - pw.println(" mLargeClockFrame: " + mLargeClockFrame); - pw.println(" mLargeClockFrame.alpha: " + mLargeClockFrame.getAlpha()); - pw.println(" mStatusArea: " + mStatusArea); - pw.println(" mDisplayedClockSize: " + mDisplayedClockSize); + pw.println(" mSmallClockFrame = " + mSmallClockFrame); + pw.println(" mSmallClockFrame.alpha = " + mSmallClockFrame.getAlpha()); + pw.println(" mLargeClockFrame = " + mLargeClockFrame); + pw.println(" mLargeClockFrame.alpha = " + mLargeClockFrame.getAlpha()); + pw.println(" mStatusArea = " + mStatusArea); + pw.println(" mDisplayedClockSize = " + mDisplayedClockSize); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 25d17928351a..ad333b7bfbb6 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -493,7 +493,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS if (mDateWeatherView != null) { mUiExecutor.execute(() -> { mDateWeatherView.setVisibility( - clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE); + clockHasCustomWeatherDataDisplay() ? View.INVISIBLE : View.VISIBLE); }); } } @@ -519,9 +519,10 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("currentClockSizeLarge=" + (mCurrentClockSize == LARGE)); - pw.println("mCanShowDoubleLineClock=" + mCanShowDoubleLineClock); + pw.println("currentClockSizeLarge: " + (mCurrentClockSize == LARGE)); + pw.println("mCanShowDoubleLineClock: " + mCanShowDoubleLineClock); mView.dump(pw, args); + mClockRegistry.dump(pw, args); ClockController clock = getClock(); if (clock != null) { clock.dump(pw); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index bec854777919..a0f5f3451e94 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -121,7 +121,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> } @Override - public void showMessage(CharSequence message, ColorStateList colorState) { + public void showMessage(CharSequence message, ColorStateList colorState, boolean animated) { } public void startAppearAnimation() { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index 5c56aab9a611..39225fb03939 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -333,14 +333,14 @@ public class KeyguardPatternViewController } @Override - public void showMessage(CharSequence message, ColorStateList colorState) { + public void showMessage(CharSequence message, ColorStateList colorState, boolean animated) { if (mMessageAreaController == null) { return; } if (colorState != null) { mMessageAreaController.setNextMessageColor(colorState); } - mMessageAreaController.setMessage(message); + mMessageAreaController.setMessage(message, animated); } @Override diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 87a775866faf..76e051ea25f3 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -65,7 +65,6 @@ 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; @@ -75,6 +74,7 @@ import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; @@ -115,6 +115,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard private final SessionTracker mSessionTracker; private final Optional<SideFpsController> mSideFpsController; private final FalsingA11yDelegate mFalsingA11yDelegate; + private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; private int mTranslationY; // Whether the volume keys should be handled by keyguard. If true, then // they will be handled here for specific media types such as music, otherwise @@ -300,11 +301,12 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard @Override public void onSwipeUp() { if (!mUpdateMonitor.isFaceDetectionRunning()) { + mKeyguardFaceAuthInteractor.onSwipeUpOnBouncer(); boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth( FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER); mKeyguardSecurityCallback.userActivity(); if (didFaceAuthRun) { - showMessage(null, null); + showMessage(/* message= */ null, /* colorState= */ null, /* animated= */ true); } } if (mUpdateMonitor.isFaceEnrolled()) { @@ -389,7 +391,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard FalsingA11yDelegate falsingA11yDelegate, TelephonyManager telephonyManager, ViewMediatorCallback viewMediatorCallback, - AudioManager audioManager + AudioManager audioManager, + KeyguardFaceAuthInteractor keyguardFaceAuthInteractor ) { super(view); mLockPatternUtils = lockPatternUtils; @@ -414,6 +417,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mTelephonyManager = telephonyManager; mViewMediatorCallback = viewMediatorCallback; mAudioManager = audioManager; + mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; } @Override @@ -454,7 +458,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard showPrimarySecurityScreen(true); mAdminSecondaryLockScreenController.hide(); if (mCurrentSecurityMode != SecurityMode.None) { - getCurrentSecurityController().onPause(); + getCurrentSecurityController(controller -> controller.onPause()); } mView.onPause(); mView.clearFocus(); @@ -508,13 +512,15 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (reason != PROMPT_REASON_NONE) { Log.i(TAG, "Strong auth required, reason: " + reason); } - getCurrentSecurityController().showPromptReason(reason); + getCurrentSecurityController(controller -> controller.showPromptReason(reason)); } } - public void showMessage(CharSequence message, ColorStateList colorState) { + /** Set message of bouncer title. */ + public void showMessage(CharSequence message, ColorStateList colorState, boolean animated) { if (mCurrentSecurityMode != SecurityMode.None) { - getCurrentSecurityController().showMessage(message, colorState); + getCurrentSecurityController( + controller -> controller.showMessage(message, colorState, animated)); } } @@ -629,7 +635,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, state); - getCurrentSecurityController().onResume(reason); + + getCurrentSecurityController(controller -> controller.onResume(reason)); } mView.onResume( mSecurityModel.getSecurityMode(KeyguardUpdateMonitor.getCurrentUser()), @@ -640,7 +647,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void setInitialMessage() { CharSequence customMessage = mViewMediatorCallback.consumeCustomMessage(); if (!TextUtils.isEmpty(customMessage)) { - showMessage(customMessage, Utils.getColorError(getContext())); + showMessage(customMessage, /* colorState= */ null, /* animated= */ false); return; } showPromptReason(mViewMediatorCallback.getBouncerPromptReason()); @@ -669,7 +676,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard if (mCurrentSecurityMode != SecurityMode.None) { setAlpha(1f); mView.startAppearAnimation(mCurrentSecurityMode); - getCurrentSecurityController().startAppearAnimation(); + getCurrentSecurityController(controller -> controller.startAppearAnimation()); } } @@ -679,24 +686,23 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } public boolean startDisappearAnimation(Runnable onFinishRunnable) { - boolean didRunAnimation = false; - if (mCurrentSecurityMode != SecurityMode.None) { mView.startDisappearAnimation(mCurrentSecurityMode); - didRunAnimation = getCurrentSecurityController().startDisappearAnimation( - onFinishRunnable); - } - - if (!didRunAnimation && onFinishRunnable != null) { - onFinishRunnable.run(); + getCurrentSecurityController( + controller -> { + boolean didRunAnimation = controller.startDisappearAnimation( + onFinishRunnable); + if (!didRunAnimation && onFinishRunnable != null) { + onFinishRunnable.run(); + } + }); } - - return didRunAnimation; + return true; } public void onStartingToHide() { if (mCurrentSecurityMode != SecurityMode.None) { - getCurrentSecurityController().onStartingToHide(); + getCurrentSecurityController(controller -> controller.onStartingToHide()); } } @@ -804,8 +810,9 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard return finish; } + @Override public boolean needsInput() { - return getCurrentSecurityController().needsInput(); + return false; } /** @@ -933,22 +940,19 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard return; } - KeyguardInputViewController<KeyguardInputView> oldView = getCurrentSecurityController(); + getCurrentSecurityController(oldView -> oldView.onPause()); - // Emulate Activity life cycle - if (oldView != null) { - oldView.onPause(); - } + mCurrentSecurityMode = securityMode; - KeyguardInputViewController<KeyguardInputView> newView = changeSecurityMode(securityMode); - if (newView != null) { - newView.onResume(KeyguardSecurityView.VIEW_REVEALED); - mSecurityViewFlipperController.show(newView); - configureMode(); - } + getCurrentSecurityController( + newView -> { + newView.onResume(KeyguardSecurityView.VIEW_REVEALED); + mSecurityViewFlipperController.show(newView); + configureMode(); + mKeyguardSecurityCallback.onSecurityModeChanged( + securityMode, newView != null && newView.needsInput()); - mKeyguardSecurityCallback.onSecurityModeChanged( - securityMode, newView != null && newView.needsInput()); + }); } /** @@ -981,7 +985,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController, () -> showMessage(getContext().getString(R.string.keyguard_unlock_to_continue), - null), mFalsingA11yDelegate); + /* colorState= */ null, /* animated= */ true), mFalsingA11yDelegate); } public void reportFailedUnlockAttempt(int userId, int timeoutMs) { @@ -1028,15 +1032,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } } - private KeyguardInputViewController<KeyguardInputView> getCurrentSecurityController() { - return mSecurityViewFlipperController - .getSecurityView(mCurrentSecurityMode, mKeyguardSecurityCallback); - } - - private KeyguardInputViewController<KeyguardInputView> changeSecurityMode( - SecurityMode securityMode) { - mCurrentSecurityMode = securityMode; - return getCurrentSecurityController(); + private void getCurrentSecurityController( + KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback) { + mSecurityViewFlipperController + .getSecurityView(mCurrentSecurityMode, mKeyguardSecurityCallback, + onViewInflatedCallback); } /** @@ -1086,28 +1086,22 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } private void reloadColors() { - reinflateViewFlipper(() -> mView.reloadColors()); + reinflateViewFlipper(controller -> mView.reloadColors()); } /** Handles density or font scale changes. */ private void onDensityOrFontScaleChanged() { - reinflateViewFlipper(() -> mView.onDensityOrFontScaleChanged()); + reinflateViewFlipper(controller -> mView.onDensityOrFontScaleChanged()); } /** * Reinflate the view flipper child view. */ public void reinflateViewFlipper( - KeyguardSecurityViewFlipperController.OnViewInflatedListener onViewInflatedListener) { + KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedListener) { mSecurityViewFlipperController.clearViews(); - if (mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)) { - mSecurityViewFlipperController.asynchronouslyInflateView(mCurrentSecurityMode, - mKeyguardSecurityCallback, onViewInflatedListener); - } else { - mSecurityViewFlipperController.getSecurityView(mCurrentSecurityMode, - mKeyguardSecurityCallback); - onViewInflatedListener.onViewInflated(); - } + mSecurityViewFlipperController.asynchronouslyInflateView(mCurrentSecurityMode, + mKeyguardSecurityCallback, onViewInflatedListener); } /** diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java index 67d77e53738a..22ad725faaa2 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java @@ -106,7 +106,7 @@ public interface KeyguardSecurityView { * @param message the message to show * @param colorState the color to use */ - void showMessage(CharSequence message, ColorStateList colorState); + void showMessage(CharSequence message, ColorStateList colorState, boolean animated); /** * Starts the animation which should run when the security view appears. diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java index ddf11997d3a7..fbacd6818648 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java @@ -28,7 +28,6 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.keyguard.dagger.KeyguardBouncerScope; import com.android.systemui.R; import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.util.ViewController; import java.util.ArrayList; @@ -54,23 +53,19 @@ public class KeyguardSecurityViewFlipperController private final Factory mKeyguardSecurityViewControllerFactory; private final FeatureFlags mFeatureFlags; - private final ViewMediatorCallback mViewMediatorCallback; - @Inject protected KeyguardSecurityViewFlipperController(KeyguardSecurityViewFlipper view, LayoutInflater layoutInflater, AsyncLayoutInflater asyncLayoutInflater, KeyguardInputViewController.Factory keyguardSecurityViewControllerFactory, EmergencyButtonController.Factory emergencyButtonControllerFactory, - FeatureFlags featureFlags, - ViewMediatorCallback viewMediatorCallback) { + FeatureFlags featureFlags) { super(view); mKeyguardSecurityViewControllerFactory = keyguardSecurityViewControllerFactory; mLayoutInflater = layoutInflater; mEmergencyButtonControllerFactory = emergencyButtonControllerFactory; mAsyncLayoutInflater = asyncLayoutInflater; mFeatureFlags = featureFlags; - mViewMediatorCallback = viewMediatorCallback; } @Override @@ -97,40 +92,17 @@ public class KeyguardSecurityViewFlipperController @VisibleForTesting - KeyguardInputViewController<KeyguardInputView> getSecurityView(SecurityMode securityMode, - KeyguardSecurityCallback keyguardSecurityCallback) { - KeyguardInputViewController<KeyguardInputView> childController = null; + void getSecurityView(SecurityMode securityMode, + KeyguardSecurityCallback keyguardSecurityCallback, + OnViewInflatedCallback onViewInflatedCallback) { for (KeyguardInputViewController<KeyguardInputView> child : mChildren) { if (child.getSecurityMode() == securityMode) { - childController = child; - break; - } - } - - if (!mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER) && childController == null - && securityMode != SecurityMode.None && securityMode != SecurityMode.Invalid) { - int layoutId = getLayoutIdFor(securityMode); - KeyguardInputView view = null; - if (layoutId != 0) { - if (DEBUG) Log.v(TAG, "inflating on main thread id = " + layoutId); - view = (KeyguardInputView) mLayoutInflater.inflate( - layoutId, mView, false); - mView.addView(view); - childController = mKeyguardSecurityViewControllerFactory.create( - view, securityMode, keyguardSecurityCallback); - childController.init(); - - mChildren.add(childController); + onViewInflatedCallback.onViewInflated(child); + return; } } - if (childController == null) { - childController = new NullKeyguardInputViewController( - securityMode, keyguardSecurityCallback, - mEmergencyButtonControllerFactory.create(null)); - } - - return childController; + asynchronouslyInflateView(securityMode, keyguardSecurityCallback, onViewInflatedCallback); } /** @@ -143,7 +115,7 @@ public class KeyguardSecurityViewFlipperController */ public void asynchronouslyInflateView(SecurityMode securityMode, KeyguardSecurityCallback keyguardSecurityCallback, - @Nullable OnViewInflatedListener onViewInflatedListener) { + @Nullable OnViewInflatedCallback onViewInflatedListener) { int layoutId = getLayoutIdFor(securityMode); if (layoutId != 0) { if (DEBUG) Log.v(TAG, "inflating on bg thread id = " + layoutId); @@ -156,9 +128,8 @@ public class KeyguardSecurityViewFlipperController keyguardSecurityCallback); childController.init(); mChildren.add(childController); - mViewMediatorCallback.setNeedsInput(childController.needsInput()); if (onViewInflatedListener != null) { - onViewInflatedListener.onViewInflated(); + onViewInflatedListener.onViewInflated(childController); } }); } @@ -184,33 +155,9 @@ public class KeyguardSecurityViewFlipperController } } - private static class NullKeyguardInputViewController - extends KeyguardInputViewController<KeyguardInputView> { - protected NullKeyguardInputViewController(SecurityMode securityMode, - KeyguardSecurityCallback keyguardSecurityCallback, - EmergencyButtonController emergencyButtonController) { - super(null, securityMode, keyguardSecurityCallback, emergencyButtonController, - null); - } - - @Override - public boolean needsInput() { - return false; - } - - @Override - public void onStartingToHide() { - } - - @Override - protected int getInitialMessageResId() { - return 0; - } - } - /** Listener to when view has finished inflation. */ - public interface OnViewInflatedListener { + public interface OnViewInflatedCallback { /** Notifies that view has been inflated */ - void onViewInflated(); + void onViewInflated(KeyguardInputViewController<KeyguardInputView> controller); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java index b8e196fb8787..d8e1eb0f0860 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java @@ -19,10 +19,12 @@ package com.android.keyguard; import static java.util.Collections.emptySet; import android.content.Context; +import android.os.Build; import android.os.Trace; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.view.ViewPropertyAnimator; import android.widget.GridLayout; import com.android.systemui.R; @@ -118,6 +120,16 @@ public class KeyguardStatusView extends GridLayout { } @Override + public ViewPropertyAnimator animate() { + if (Build.IS_DEBUGGABLE) { + throw new IllegalArgumentException( + "KeyguardStatusView does not support ViewPropertyAnimator. " + + "Use PropertyAnimator instead."); + } + return super.animate(); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { Trace.beginSection("KeyguardStatusView#onMeasure"); super.onMeasure(widthMeasureSpec, heightMeasureSpec); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 350c4ed084b9..c48aaf451e62 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -154,6 +154,16 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpsysTableLogger; +import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener; +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; +import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent; +import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.AuthenticationStatus; +import com.android.systemui.keyguard.shared.model.DetectionStatus; +import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus; +import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus; import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.WeatherData; @@ -372,6 +382,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final FingerprintManager mFpm; @Nullable private final FaceManager mFaceManager; + @Nullable + private KeyguardFaceAuthInteractor mFaceAuthInteractor; private final LockPatternUtils mLockPatternUtils; @VisibleForTesting @DevicePostureInt @@ -526,6 +538,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.logTrustGrantedWithFlags(flags, newlyUnlocked, userId, message); if (userId == getCurrentUser()) { + if (newlyUnlocked) { + // if this callback is ever removed, this should then be logged in + // TrustRepository + mUiEventLogger.log( + TrustAgentUiEvent.TRUST_AGENT_NEWLY_UNLOCKED, + getKeyguardSessionId() + ); + } final TrustGrantFlags trustGrantFlags = new TrustGrantFlags(flags); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); @@ -1165,8 +1185,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Trace.endSection(); } + /** + * @deprecated This is being migrated to use modern architecture, this method is visible purely + * for bridging the gap while the migration is active. + */ private void handleFaceAuthFailed() { Assert.isMainThread(); + String reason = + mKeyguardBypassController.canBypass() ? "bypass" + : mAlternateBouncerShowing ? "alternateBouncer" + : mPrimaryBouncerFullyShown ? "bouncer" + : "udfpsFpDown"; + requestActiveUnlock( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL, + "faceFailure-" + reason); + mLogger.d("onFaceAuthFailed"); mFaceCancelSignal = null; setFaceRunningState(BIOMETRIC_STATE_STOPPED); @@ -1180,6 +1213,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mContext.getString(R.string.kg_face_not_recognized)); } + /** + * @deprecated This is being migrated to use modern architecture, this method is visible purely + * for bridging the gap while the migration is active. + */ private void handleFaceAcquired(int acquireInfo) { Assert.isMainThread(); mLogger.logFaceAcquired(acquireInfo); @@ -1189,8 +1226,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab cb.onBiometricAcquired(FACE, acquireInfo); } } + + if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + acquireInfo)) { + requestActiveUnlock( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL, + "faceAcquireInfo-" + acquireInfo); + } } + /** + * @deprecated This is being migrated to use modern architecture, this method is visible purely + * for bridging the gap while the migration is active. + */ private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) { Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated"); try { @@ -1203,7 +1251,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.logFaceAuthForWrongUser(authUserId); return; } - if (isFaceDisabled(userId)) { + if (!isFaceAuthInteractorEnabled() && isFaceDisabled(userId)) { mLogger.logFaceAuthDisabledForUser(userId); return; } @@ -1215,7 +1263,14 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab Trace.endSection(); } + /** + * @deprecated This is being migrated to use modern architecture, this method is visible purely + * for bridging the gap while the migration is active. + */ private void handleFaceHelp(int msgId, String helpString) { + if (mFaceAcquiredInfoIgnoreList.contains(msgId)) { + return; + } Assert.isMainThread(); mLogger.logFaceAuthHelpMsg(msgId, helpString); for (int i = 0; i < mCallbacks.size(); i++) { @@ -1226,22 +1281,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } } - private final Runnable mRetryFaceAuthentication = new Runnable() { - @Override - public void run() { - mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount); - updateFaceListeningState(BIOMETRIC_ACTION_UPDATE, - FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE); - } - }; - - private void onFaceCancelNotReceived() { - mLogger.e("Face cancellation not received, transitioning to STOPPED"); - mFaceRunningState = BIOMETRIC_STATE_STOPPED; - KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP, - FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED); - } - + /** + * @deprecated This is being migrated to use modern architecture, this method is visible purely + * for bridging the gap while the migration is active. + */ private void handleFaceError(int msgId, final String originalErrMsg) { Assert.isMainThread(); String errString = originalErrMsg; @@ -1299,6 +1342,28 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab if (lockedOutStateChanged) { notifyLockedOutStateChanged(FACE); } + + if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(msgId)) { + requestActiveUnlock( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL, + "faceError-" + msgId); + } + } + + private final Runnable mRetryFaceAuthentication = new Runnable() { + @Override + public void run() { + mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount); + updateFaceListeningState(BIOMETRIC_ACTION_UPDATE, + FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE); + } + }; + + private void onFaceCancelNotReceived() { + mLogger.e("Face cancellation not received, transitioning to STOPPED"); + mFaceRunningState = BIOMETRIC_STATE_STOPPED; + KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP, + FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED); } private void handleFaceLockoutReset(@LockoutMode int mode) { @@ -1343,10 +1408,61 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return mFingerprintRunningState == BIOMETRIC_STATE_RUNNING; } + /** + * @deprecated This is being migrated to use modern architecture. + */ + @Deprecated public boolean isFaceDetectionRunning() { + if (isFaceAuthInteractorEnabled()) { + return getFaceAuthInteractor().isRunning(); + } return mFaceRunningState == BIOMETRIC_STATE_RUNNING; } + private boolean isFaceAuthInteractorEnabled() { + return mFaceAuthInteractor != null && mFaceAuthInteractor.isEnabled(); + } + + private @Nullable KeyguardFaceAuthInteractor getFaceAuthInteractor() { + return mFaceAuthInteractor; + } + + /** + * Set the face auth interactor that should be used for initiating face authentication. + */ + public void setFaceAuthInteractor(@Nullable KeyguardFaceAuthInteractor faceAuthInteractor) { + mFaceAuthInteractor = faceAuthInteractor; + mFaceAuthInteractor.registerListener(mFaceAuthenticationListener); + } + + private FaceAuthenticationListener mFaceAuthenticationListener = + new FaceAuthenticationListener() { + @Override + public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) { + if (status instanceof AcquiredAuthenticationStatus) { + handleFaceAcquired( + ((AcquiredAuthenticationStatus) status).getAcquiredInfo()); + } else if (status instanceof ErrorAuthenticationStatus) { + ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status; + handleFaceError(error.getMsgId(), error.getMsg()); + } else if (status instanceof FailedAuthenticationStatus) { + handleFaceAuthFailed(); + } else if (status instanceof HelpAuthenticationStatus) { + HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status; + handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg()); + } else if (status instanceof SuccessAuthenticationStatus) { + FaceManager.AuthenticationResult result = + ((SuccessAuthenticationStatus) status).getSuccessResult(); + handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric()); + } + } + + @Override + public void onDetectionStatusChanged(@NonNull DetectionStatus status) { + handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric()); + } + }; + private boolean isTrustDisabled() { // Don't allow trust agent if device is secured with a SIM PIN. This is here // mainly because there's no other way to prompt the user to enter their SIM PIN @@ -1360,6 +1476,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab || isSimPinSecure(); } + /** + * @deprecated This method is not needed anymore with the new face auth system. + */ + @Deprecated private boolean isFaceDisabled(int userId) { // TODO(b/140035044) return whitelistIpcs(() -> @@ -1371,7 +1491,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab /** * @return whether the current user has been authenticated with face. This may be true * on the lockscreen if the user doesn't have bypass enabled. + * + * @deprecated This is being migrated to use modern architecture. */ + @Deprecated public boolean getIsFaceAuthenticated() { boolean faceAuthenticated = false; BiometricAuthenticated bioFaceAuthenticated = mUserFaceAuthenticated.get(getCurrentUser()); @@ -1619,6 +1742,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab void setAssistantVisible(boolean assistantVisible) { mAssistantVisible = assistantVisible; mLogger.logAssistantVisible(mAssistantVisible); + if (isFaceAuthInteractorEnabled()) { + mFaceAuthInteractor.onAssistantTriggeredOnLockScreen(); + } updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE, FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED); if (mAssistantVisible) { @@ -1832,54 +1958,27 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationFailed() { - String reason = - mKeyguardBypassController.canBypass() ? "bypass" - : mAlternateBouncerShowing ? "alternateBouncer" - : mPrimaryBouncerFullyShown ? "bouncer" - : "udfpsFpDown"; - requestActiveUnlock( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL, - "faceFailure-" + reason); - handleFaceAuthFailed(); } @Override public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) { - Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded"); handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric()); - Trace.endSection(); } @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { - if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) { - return; - } handleFaceHelp(helpMsgId, helpString.toString()); } @Override public void onAuthenticationError(int errMsgId, CharSequence errString) { handleFaceError(errMsgId, errString.toString()); - - if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) { - requestActiveUnlock( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL, - "faceError-" + errMsgId); - } } @Override public void onAuthenticationAcquired(int acquireInfo) { handleFaceAcquired(acquireInfo); - - if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( - acquireInfo)) { - requestActiveUnlock( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL, - "faceAcquireInfo-" + acquireInfo); - } } }; @@ -2628,7 +2727,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being * invoked. * @return current face auth detection state, true if it is running. + * @deprecated This is being migrated to use modern architecture. */ + @Deprecated public boolean requestFaceAuth(@FaceAuthApiRequestReason String reason) { mLogger.logFaceAuthRequested(reason); updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason)); @@ -2643,6 +2744,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) { + if (isFaceAuthInteractorEnabled()) return; // If this message exists, we should not authenticate again until this message is // consumed by the handler if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) { @@ -3154,6 +3256,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } public boolean isFaceLockedOut() { + if (isFaceAuthInteractorEnabled()) { + return getFaceAuthInteractor().isLockedOut(); + } return mFaceLockedOutPermanent; } @@ -3202,13 +3307,23 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return mIsUnlockWithFingerprintPossible.getOrDefault(userId, false); } + /** + * @deprecated This is being migrated to use modern architecture. + */ + @Deprecated private boolean isUnlockWithFacePossible(int userId) { + if (isFaceAuthInteractorEnabled()) { + return getFaceAuthInteractor().canFaceAuthRun(); + } return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId); } /** * If face hardware is available, user has enrolled and enabled auth via setting. + * + * @deprecated This is being migrated to use modern architecture. */ + @Deprecated public boolean isFaceAuthEnabledForUser(int userId) { // TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once. updateFaceEnrolled(userId); @@ -3232,6 +3347,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private void stopListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) { + if (isFaceAuthInteractorEnabled()) return; mLogger.v("stopListeningForFace()"); mLogger.logStoppedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason()); mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId()); @@ -4098,6 +4214,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mStatusBarStateController.removeCallback(mStatusBarStateControllerListener); mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener); mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener); + if (isFaceAuthInteractorEnabled()) { + mFaceAuthInteractor.unregisterListener(mFaceAuthenticationListener); + } if (mDeviceProvisionedObserver != null) { mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver); @@ -4127,6 +4246,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab pw.println(" getUserHasTrust()=" + getUserHasTrust(getCurrentUser())); pw.println(" getUserUnlockedWithBiometric()=" + getUserUnlockedWithBiometric(getCurrentUser())); + pw.println(" isFaceAuthInteractorEnabled: " + isFaceAuthInteractorEnabled()); pw.println(" SIM States:"); for (SimData data : mSimDatas.values()) { pw.println(" " + data.toString()); @@ -4262,7 +4382,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab * Cancels all operations in the scheduler if it is hung for 10 seconds. */ public void startBiometricWatchdog() { - if (mFaceManager != null) { + if (mFaceManager != null && !isFaceAuthInteractorEnabled()) { mFaceManager.scheduleWatchdog(); } if (mFpm != null) { diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java index a678edc0eb06..651c9796140e 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardVisibilityHelper.java @@ -18,8 +18,8 @@ package com.android.keyguard; import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; +import android.util.Property; import android.view.View; -import android.view.ViewPropertyAnimator; import com.android.systemui.animation.Interpolators; import com.android.systemui.plugins.log.LogBuffer; @@ -34,6 +34,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController; import com.google.errorprone.annotations.CompileTimeConstant; +import java.util.function.Consumer; + /** * Helper class for updating visibility of keyguard views based on keyguard and status bar state. * This logic is shared by both the keyguard status view and the keyguard user switcher. @@ -83,47 +85,49 @@ public class KeyguardVisibilityHelper { boolean keyguardFadingAway, boolean goingToFullShade, int oldStatusBarState) { - mView.animate().cancel(); + PropertyAnimator.cancelAnimation(mView, AnimatableProperty.ALPHA); boolean isOccluded = mKeyguardStateController.isOccluded(); mKeyguardViewVisibilityAnimating = false; if ((!keyguardFadingAway && oldStatusBarState == KEYGUARD && statusBarState != KEYGUARD) || goingToFullShade) { mKeyguardViewVisibilityAnimating = true; - mView.animate() - .alpha(0f) - .setStartDelay(0) - .setDuration(160) - .setInterpolator(Interpolators.ALPHA_OUT) - .withEndAction( - mAnimateKeyguardStatusViewGoneEndRunnable); + + AnimationProperties animProps = new AnimationProperties() + .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_OUT) + .setAnimationEndAction(mSetGoneEndAction); if (keyguardFadingAway) { - mView.animate() - .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay()) - .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration()) - .start(); + animProps + .setDelay(mKeyguardStateController.getKeyguardFadingAwayDelay()) + .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration()); log("goingToFullShade && keyguardFadingAway"); } else { + animProps.setDelay(0).setDuration(160); log("goingToFullShade && !keyguardFadingAway"); } + PropertyAnimator.setProperty( + mView, AnimatableProperty.ALPHA, 0f, animProps, true /* animate */); } else if (oldStatusBarState == StatusBarState.SHADE_LOCKED && statusBarState == KEYGUARD) { mView.setVisibility(View.VISIBLE); mKeyguardViewVisibilityAnimating = true; mView.setAlpha(0f); - mView.animate() - .alpha(1f) - .setStartDelay(0) - .setDuration(320) - .setInterpolator(Interpolators.ALPHA_IN) - .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable); + PropertyAnimator.setProperty( + mView, AnimatableProperty.ALPHA, 1f, + new AnimationProperties() + .setDelay(0) + .setDuration(320) + .setCustomInterpolator(View.ALPHA, Interpolators.ALPHA_IN) + .setAnimationEndAction( + property -> mSetVisibleEndRunnable.run()), + true /* animate */); log("keyguardFadingAway transition w/ Y Aniamtion"); } else if (statusBarState == KEYGUARD) { if (keyguardFadingAway) { mKeyguardViewVisibilityAnimating = true; - ViewPropertyAnimator animator = mView.animate() - .alpha(0) - .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN) - .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable); + AnimationProperties animProps = new AnimationProperties() + .setDelay(0) + .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_LINEAR_IN) + .setAnimationEndAction(mSetInvisibleEndAction); if (mAnimateYPos) { float target = mView.getY() - mView.getHeight() * 0.05f; int delay = 0; @@ -135,13 +139,16 @@ public class KeyguardVisibilityHelper { PropertyAnimator.setProperty(mView, AnimatableProperty.Y, target, mAnimationProperties, true /* animate */); - animator.setDuration(duration) - .setStartDelay(delay); + animProps.setDuration(duration) + .setDelay(delay); log("keyguardFadingAway transition w/ Y Aniamtion"); } else { log("keyguardFadingAway transition w/o Y Animation"); } - animator.start(); + PropertyAnimator.setProperty( + mView, AnimatableProperty.ALPHA, 0f, + animProps, + true /* animate */); } else if (mScreenOffAnimationController.shouldAnimateInKeyguard()) { log("ScreenOff transition"); mKeyguardViewVisibilityAnimating = true; @@ -149,7 +156,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( - mView, mAnimateKeyguardStatusViewVisibleEndRunnable); + mView, mSetVisibleEndRunnable); } else { log("Direct set Visibility to VISIBLE"); mView.setVisibility(View.VISIBLE); @@ -163,19 +170,25 @@ public class KeyguardVisibilityHelper { mLastOccludedState = isOccluded; } - private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = () -> { - mKeyguardViewVisibilityAnimating = false; - mView.setVisibility(View.INVISIBLE); - log("Callback Set Visibility to INVISIBLE"); + private final Consumer<Property> mSetInvisibleEndAction = new Consumer<>() { + @Override + public void accept(Property property) { + mKeyguardViewVisibilityAnimating = false; + mView.setVisibility(View.INVISIBLE); + log("Callback Set Visibility to INVISIBLE"); + } }; - private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = () -> { - mKeyguardViewVisibilityAnimating = false; - mView.setVisibility(View.GONE); - log("CallbackSet Visibility to GONE"); + private final Consumer<Property> mSetGoneEndAction = new Consumer<>() { + @Override + public void accept(Property property) { + mKeyguardViewVisibilityAnimating = false; + mView.setVisibility(View.GONE); + log("CallbackSet Visibility to GONE"); + } }; - private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = () -> { + private final Runnable mSetVisibleEndRunnable = () -> { mKeyguardViewVisibilityAnimating = false; mView.setVisibility(View.VISIBLE); log("Callback Set Visibility to VISIBLE"); diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java index 334bb1ec12cb..9308773858e3 100644 --- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java @@ -96,6 +96,11 @@ public interface ViewMediatorCallback { CharSequence consumeCustomMessage(); /** + * Sets a message to be consumed the next time the bouncer shows up. + */ + void setCustomMessage(CharSequence customMessage); + + /** * Call when cancel button is pressed in bouncer. */ void onCancelClicked(); diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index 6e98a1805d62..cde8ff78c620 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -33,6 +33,7 @@ import com.android.systemui.shared.clocks.DefaultClockProvider; import dagger.Module; import dagger.Provides; + import kotlinx.coroutines.CoroutineDispatcher; import kotlinx.coroutines.CoroutineScope; diff --git a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java index 7af6f6677f3f..401f6c9c747d 100644 --- a/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/ActivityStarterDelegate.java @@ -26,12 +26,12 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.statusbar.phone.CentralSurfaces; +import dagger.Lazy; + import java.util.Optional; import javax.inject.Inject; -import dagger.Lazy; - /** * Single common instance of ActivityStarter that can be gotten and referenced from anywhere, but * delegates to an actual implementation (CentralSurfaces). @@ -142,6 +142,14 @@ public class ActivityStarterDelegate implements ActivityStarter { } @Override + public void postStartActivityDismissingKeyguard(Intent intent, int delay, + @Nullable ActivityLaunchAnimator.Controller animationController, String customMessage) { + mActualStarterOptionalLazy.get().ifPresent( + starter -> starter.postStartActivityDismissingKeyguard(intent, delay, + animationController, customMessage)); + } + + @Override public void postStartActivityDismissingKeyguard(PendingIntent intent, ActivityLaunchAnimator.Controller animationController) { mActualStarterOptionalLazy.get().ifPresent( @@ -161,4 +169,12 @@ public class ActivityStarterDelegate implements ActivityStarter { mActualStarterOptionalLazy.get().ifPresent( starter -> starter.dismissKeyguardThenExecute(action, cancel, afterKeyguardGone)); } + + @Override + public void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel, + boolean afterKeyguardGone, String customMessage) { + mActualStarterOptionalLazy.get().ifPresent( + starter -> starter.dismissKeyguardThenExecute(action, cancel, afterKeyguardGone, + customMessage)); + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index e42f051bf3bb..517f94fbe938 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -637,6 +637,7 @@ public class AuthContainerView extends LinearLayout @Override public void onDetachedFromWindow() { + mPanelInteractionDetector.disable(); OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher(); if (dispatcher != null) { findOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback); @@ -674,7 +675,6 @@ public class AuthContainerView extends LinearLayout @Override public void dismissWithoutCallback(boolean animate) { - mPanelInteractionDetector.disable(); if (animate) { animateAway(false /* sendReason */, 0 /* reason */); } else { @@ -685,7 +685,6 @@ public class AuthContainerView extends LinearLayout @Override public void dismissFromSystemServer() { - mPanelInteractionDetector.disable(); animateAway(false /* sendReason */, 0 /* reason */); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt index d15a2afa0d4a..b72801d3b5fe 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt @@ -30,6 +30,7 @@ constructor( @MainThread fun disable() { if (action != null) { + Log.i(TAG, "Disable dectector") action = null shadeExpansionStateManager.removeExpansionListener(this::onPanelExpansionChanged) } @@ -39,8 +40,8 @@ constructor( private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) = mainExecutor.execute { action?.let { - if (event.tracking || event.expanded) { - Log.v(TAG, "Detected panel interaction, event: $event") + if (event.tracking || (event.expanded && event.fraction > 0)) { + Log.i(TAG, "Detected panel interaction, event: $event") it.onPanelInteraction.run() disable() } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index eb5d23a23abb..4319f01bed14 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -144,8 +144,7 @@ constructor( orientationListener.enable() } } - @VisibleForTesting - internal var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT + @VisibleForTesting var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT private val overlayViewParams = WindowManager.LayoutParams( @@ -297,7 +296,7 @@ constructor( } @VisibleForTesting - internal fun updateOverlayParams(display: Display, bounds: Rect) { + fun updateOverlayParams(display: Display, bounds: Rect) { val isNaturalOrientation = display.isNaturalOrientation() val isDefaultOrientation = if (isReverseDefaultRotation) !isNaturalOrientation else isNaturalOrientation diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index aabdafb88558..7a237591a212 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -83,6 +83,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; @@ -151,6 +152,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final DumpManager mDumpManager; @NonNull private final SystemUIDialogManager mDialogManager; @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; + @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; @NonNull private final VibratorHelper mVibrator; @NonNull private final FeatureFlags mFeatureFlags; @NonNull private final FalsingManager mFalsingManager; @@ -818,7 +820,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull AlternateBouncerInteractor alternateBouncerInteractor, @NonNull SecureSettings secureSettings, @NonNull InputManager inputManager, - @NonNull UdfpsUtils udfpsUtils) { + @NonNull UdfpsUtils udfpsUtils, + @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) { mContext = context; mExecution = execution; mVibrator = vibrator; @@ -882,6 +885,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } return Unit.INSTANCE; }); + mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController(); mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController); @@ -1141,6 +1145,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (!mOnFingerDown) { playStartHaptic(); + mKeyguardFaceAuthInteractor.onUdfpsSensorTouched(); if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) { mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt index 5101ad4c94bc..3b50bbcd9251 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt @@ -257,7 +257,7 @@ constructor( } @VisibleForTesting - internal suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job { + suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job { return scope.launch { primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float -> inputBouncerExpansion = bouncerExpansion @@ -268,7 +268,7 @@ constructor( } @VisibleForTesting - internal suspend fun listenForAlternateBouncerVisibility(scope: CoroutineScope): Job { + suspend fun listenForAlternateBouncerVisibility(scope: CoroutineScope): Job { return scope.launch { alternateBouncerInteractor.isVisible.collect { isVisible: Boolean -> showUdfpsBouncer(isVisible) diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java index 25b1e3a8eed2..83e61d69e4f3 100644 --- a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java @@ -155,8 +155,7 @@ public class BroadcastDialog extends SystemUIDialog { } @Override - public void onStart() { - super.onStart(); + public void start() { registerBroadcastCallBack(mExecutor, mBroadcastCallback); } @@ -200,8 +199,7 @@ public class BroadcastDialog extends SystemUIDialog { } @Override - public void onStop() { - super.onStop(); + public void stop() { unregisterBroadcastCallBack(mBroadcastCallback); } diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt index 9e15c7e31127..f17d0f307473 100644 --- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialog.kt @@ -86,13 +86,11 @@ class ContrastDialog( highlightContrast(toContrastLevel(initialContrast)) } - override fun onStart() { - super.onStart() + override fun start() { uiModeManager.addContrastChangeListener(mainExecutor, this) } - override fun onStop() { - super.onStop() + override fun stop() { uiModeManager.removeContrastChangeListener(this) } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsPopupMenu.kt index d08bc48e945d..f7c8e9652e4e 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsPopupMenu.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsPopupMenu.kt @@ -20,11 +20,18 @@ import android.content.Context import android.content.res.Resources import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable -import android.view.Gravity +import android.view.Gravity.END +import android.view.Gravity.GravityFlags +import android.view.Gravity.NO_GRAVITY +import android.view.Gravity.START import android.view.View +import android.view.View.MeasureSpec +import android.view.ViewGroup import android.widget.ListPopupWindow +import android.widget.ListView import android.widget.PopupWindow import com.android.systemui.R +import kotlin.math.max class ControlsPopupMenu(context: Context) : ListPopupWindow(context) { @@ -40,13 +47,13 @@ class ControlsPopupMenu(context: Context) : ListPopupWindow(context) { private val dimDrawable: Drawable = ColorDrawable(resources.getColor(R.color.control_popup_dim)) private var dismissListener: PopupWindow.OnDismissListener? = null + @GravityFlags private var dropDownGravity: Int = NO_GRAVITY init { setBackgroundDrawable(dialogBackground) inputMethodMode = INPUT_METHOD_NOT_NEEDED isModal = true - setDropDownGravity(Gravity.START) // dismiss method isn't called when popup is hidden by outside touch. So we need to // override a listener to remove a dimming foreground @@ -59,30 +66,68 @@ class ControlsPopupMenu(context: Context) : ListPopupWindow(context) { override fun show() { // need to call show() first in order to construct the listView super.show() - - val paddedWidth = resources.displayMetrics.widthPixels - 2 * horizontalMargin - width = maxWidth.coerceAtMost(paddedWidth) + updateWidth() anchorView?.let { - horizontalOffset = -width / 2 + it.width / 2 - verticalOffset = -it.height / 2 - if (it.layoutDirection == View.LAYOUT_DIRECTION_RTL) { - horizontalOffset = -horizontalOffset - } - + positionPopup(it) it.rootView.foreground = dimDrawable } - with(listView!!) { clipToOutline = true background = dialogBackground dividerHeight = listDividerHeight } - // actual show takes into account updated ListView specs super.show() } + override fun setDropDownGravity(@GravityFlags gravity: Int) { + super.setDropDownGravity(gravity) + dropDownGravity = gravity + } + override fun setOnDismissListener(listener: PopupWindow.OnDismissListener?) { dismissListener = listener } + + private fun updateWidth() { + val paddedWidth = resources.displayMetrics.widthPixels - 2 * horizontalMargin + val maxWidth = maxWidth.coerceAtMost(paddedWidth) + when (width) { + ViewGroup.LayoutParams.MATCH_PARENT -> { + width = maxWidth + } + ViewGroup.LayoutParams.WRAP_CONTENT -> { + width = listView!!.measureDesiredWidth(maxWidth).coerceAtMost(maxWidth) + } + } + } + + private fun positionPopup(anchorView: View) { + when (dropDownGravity) { + NO_GRAVITY -> { + horizontalOffset = (-width + anchorView.width) / 2 + if (anchorView.layoutDirection == View.LAYOUT_DIRECTION_RTL) { + horizontalOffset = -horizontalOffset + } + } + END, + START -> { + horizontalOffset = 0 + } + } + verticalOffset = -anchorView.height / 2 + } + + private fun ListView.measureDesiredWidth(maxWidth: Int): Int { + var maxItemWidth = 0 + repeat(adapter.count) { + val view = adapter.getView(it, null, listView) + view.measure( + MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST), + MeasureSpec.UNSPECIFIED + ) + maxItemWidth = max(maxItemWidth, view.measuredWidth) + } + return maxItemWidth + } } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index d4ce9b699619..92607c6101af 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -34,6 +34,7 @@ import android.service.controls.Control import android.service.controls.ControlsProviderService import android.util.Log import android.view.ContextThemeWrapper +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -72,7 +73,6 @@ import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags -import com.android.systemui.globalactions.GlobalActionsPopupMenu import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.KeyguardStateController @@ -540,12 +540,12 @@ class ControlsUiControllerImpl @Inject constructor ( val anchor = parent.requireViewById<ImageView>(R.id.controls_more) anchor.setOnClickListener(object : View.OnClickListener { override fun onClick(v: View) { - popup = GlobalActionsPopupMenu( - popupThemedContext, - false /* isDropDownMode */ - ).apply { - setAnchorView(anchor) + popup = ControlsPopupMenu(popupThemedContext).apply { + width = ViewGroup.LayoutParams.WRAP_CONTENT + anchorView = anchor + setDropDownGravity(Gravity.END) setAdapter(adapter) + setOnItemClickListener(object : AdapterView.OnItemClickListener { override fun onItemClick( parent: AdapterView<*>, @@ -618,7 +618,8 @@ class ControlsUiControllerImpl @Inject constructor ( anchor.setOnClickListener(object : View.OnClickListener { override fun onClick(v: View) { popup = ControlsPopupMenu(popupThemedContext).apply { - setAnchorView(anchor) + anchorView = anchor + width = ViewGroup.LayoutParams.MATCH_PARENT setAdapter(adapter) setOnItemClickListener(object : AdapterView.OnItemClickListener { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 9bf6b2a5b42b..20d690e209af 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -48,6 +48,7 @@ import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController +import com.android.systemui.statusbar.phone.LetterboxModule import com.android.systemui.stylus.StylusUsiPowerStartable import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator import com.android.systemui.theme.ThemeOverlayController @@ -66,7 +67,8 @@ import dagger.multibindings.IntoMap */ @Module(includes = [ MultiUserUtilsModule::class, - StartControlsStartableModule::class + StartControlsStartableModule::class, + LetterboxModule::class, ]) abstract class SystemUICoreStartableModule { /** Inject into AuthController. */ diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt index 276a290e22ca..7d1ffca88d9f 100644 --- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt +++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt @@ -39,6 +39,11 @@ open class DumpManager @Inject constructor() { private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = ArrayMap() private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = ArrayMap() + /** See [registerCriticalDumpable]. */ + fun registerCriticalDumpable(module: Dumpable) { + registerCriticalDumpable(module::class.java.simpleName, module) + } + /** * Registers a dumpable to be called during the CRITICAL section of the bug report. * diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index f28aeadc7acb..bd982c65369d 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -67,10 +67,6 @@ object Flags { // TODO(b/254512538): Tracking Bug val INSTANT_VOICE_REPLY = unreleasedFlag(111, "instant_voice_reply") - // TODO(b/254512425): Tracking Bug - val NOTIFICATION_MEMORY_MONITOR_ENABLED = - releasedFlag(112, "notification_memory_monitor_enabled") - /** * This flag is server-controlled and should stay as [unreleasedFlag] since we never want to * enable it on release builds. @@ -229,7 +225,7 @@ object Flags { /** Whether to inflate the bouncer view on a background thread. */ // TODO(b/273341787): Tracking Bug @JvmField - val PREVENT_BYPASS_KEYGUARD = unreleasedFlag(230, "prevent_bypass_keyguard", teamfood = true) + val PREVENT_BYPASS_KEYGUARD = releasedFlag(230, "prevent_bypass_keyguard") /** Whether to use a new data source for intents to run on keyguard dismissal. */ @JvmField @@ -281,6 +277,10 @@ object Flags { @JvmField val QS_PIPELINE_NEW_HOST = unreleasedFlag(504, "qs_pipeline_new_host", teamfood = false) + // TODO(b/278068252): Tracking Bug + @JvmField + val QS_PIPELINE_AUTO_ADD = unreleasedFlag(505, "qs_pipeline_auto_add", teamfood = false) + // TODO(b/254512383): Tracking Bug @JvmField val FULL_SCREEN_USER_SWITCHER = @@ -693,6 +693,11 @@ object Flags { val KEYBOARD_BACKLIGHT_INDICATOR = unreleasedFlag(2601, "keyboard_backlight_indicator", teamfood = true) + // TODO(b/277192623): Tracking Bug + @JvmField + val KEYBOARD_EDUCATION = + unreleasedFlag(2603, "keyboard_education", teamfood = false) + // TODO(b/272036292): Tracking Bug @JvmField val LARGE_SHADE_GRANULAR_ALPHA_INTERPOLATION = diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 07753ca1e6dc..4be47ecb42d9 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -2477,8 +2477,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } @Override - protected void onStart() { - super.onStart(); + protected void start() { mGlobalActionsLayout.updateList(); if (mBackgroundDrawable instanceof ScrimDrawable) { @@ -2509,8 +2508,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene } @Override - protected void onStop() { - super.onStop(); + protected void stop() { mColorExtractor.removeOnColorsChangedListener(this); } diff --git a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt index 801b1652e487..c41b5e4d319b 100644 --- a/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt +++ b/packages/SystemUI/src/com/android/systemui/graphics/ImageLoader.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.graphics import android.annotation.AnyThread @@ -20,6 +36,7 @@ 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.Application import com.android.systemui.dagger.qualifiers.Background import java.io.IOException import javax.inject.Inject @@ -35,7 +52,7 @@ import kotlinx.coroutines.withContext class ImageLoader @Inject constructor( - private val defaultContext: Context, + @Application private val defaultContext: Context, @Background private val backgroundDispatcher: CoroutineDispatcher ) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 416b2379eef8..0fd479afcf48 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -857,6 +857,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, mCustomMessage = null; return message; } + + @Override + public void setCustomMessage(CharSequence customMessage) { + mCustomMessage = customMessage; + } }; /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt new file mode 100644 index 000000000000..a44df7e11fa1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt @@ -0,0 +1,35 @@ +/* + * 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.dagger + +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor +import com.android.systemui.keyguard.domain.interactor.NoopKeyguardFaceAuthInteractor +import dagger.Binds +import dagger.Module + +/** + * Module that provides bindings for face auth classes that are injected into SysUI components that + * are used across different SysUI variants, where face auth is not supported. + * + * Some variants that do not support face authentication can install this module to provide a no-op + * implementation of the interactor. + */ +@Module +interface KeyguardFaceAuthNotSupportedModule { + @Binds + fun keyguardFaceAuthInteractor(impl: NoopKeyguardFaceAuthInteractor): KeyguardFaceAuthInteractor +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index 0abce82527f9..5f6098b8758f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -34,6 +34,7 @@ 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.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus import com.android.systemui.keyguard.shared.model.AuthenticationStatus import com.android.systemui.keyguard.shared.model.DetectionStatus @@ -44,6 +45,8 @@ 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.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.user.data.repository.UserRepository import java.io.PrintWriter @@ -78,7 +81,7 @@ interface DeviceEntryFaceAuthRepository { val isAuthenticated: Flow<Boolean> /** Whether face auth can run at this point. */ - val canRunFaceAuth: Flow<Boolean> + val canRunFaceAuth: StateFlow<Boolean> /** Provide the current status of face authentication. */ val authenticationStatus: Flow<AuthenticationStatus> @@ -87,10 +90,10 @@ interface DeviceEntryFaceAuthRepository { val detectionStatus: Flow<DetectionStatus> /** Current state of whether face authentication is locked out or not. */ - val isLockedOut: Flow<Boolean> + val isLockedOut: StateFlow<Boolean> /** Current state of whether face authentication is running. */ - val isAuthRunning: Flow<Boolean> + val isAuthRunning: StateFlow<Boolean> /** Whether bypass is currently enabled */ val isBypassEnabled: Flow<Boolean> @@ -129,6 +132,9 @@ constructor( private val keyguardRepository: KeyguardRepository, private val keyguardInteractor: KeyguardInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, + @FaceDetectTableLog private val faceDetectLog: TableLogBuffer, + @FaceAuthTableLog private val faceAuthLog: TableLogBuffer, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, dumpManager: DumpManager, ) : DeviceEntryFaceAuthRepository, Dumpable { private var authCancellationSignal: CancellationSignal? = null @@ -207,6 +213,13 @@ constructor( observeFaceAuthGatingChecks() observeFaceDetectGatingChecks() observeFaceAuthResettingConditions() + listenForSchedulingWatchdog() + } + + private fun listenForSchedulingWatchdog() { + keyguardTransitionInteractor.anyStateToGoneTransition + .onEach { faceManager?.scheduleWatchdog() } + .launchIn(applicationScope) } private fun observeFaceAuthResettingConditions() { @@ -224,17 +237,19 @@ constructor( // Face detection can run only when lockscreen bypass is enabled // & detection is supported & biometric unlock is not allowed. listOf( - canFaceAuthOrDetectRun(), - logAndObserve(isBypassEnabled, "isBypassEnabled"), + canFaceAuthOrDetectRun(faceDetectLog), + logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog), logAndObserve( biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(), - "nonStrongBiometricIsNotAllowed" + "nonStrongBiometricIsNotAllowed", + faceDetectLog ), // 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" + "udfpsAuthIsNotPossibleAnymore", + faceDetectLog ) ) .reduce(::and) @@ -246,6 +261,7 @@ constructor( cancelDetection() } } + .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false) .launchIn(applicationScope) } @@ -254,26 +270,34 @@ constructor( it == BiometricType.UNDER_DISPLAY_FINGERPRINT } - private fun canFaceAuthOrDetectRun(): Flow<Boolean> { + private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> { return listOf( - logAndObserve(biometricSettingsRepository.isFaceEnrolled, "isFaceEnrolled"), + logAndObserve( + biometricSettingsRepository.isFaceEnrolled, + "isFaceEnrolled", + tableLogBuffer + ), logAndObserve( biometricSettingsRepository.isFaceAuthenticationEnabled, - "isFaceAuthenticationEnabled" + "isFaceAuthenticationEnabled", + tableLogBuffer ), logAndObserve( userRepository.userSwitchingInProgress.isFalse(), - "userSwitchingNotInProgress" + "userSwitchingNotInProgress", + tableLogBuffer ), logAndObserve( keyguardRepository.isKeyguardGoingAway.isFalse(), - "keyguardNotGoingAway" + "keyguardNotGoingAway", + tableLogBuffer ), logAndObserve( keyguardRepository.wakefulness .map { WakefulnessModel.isSleepingOrStartingToSleep(it) } .isFalse(), - "deviceNotSleepingOrNotStartingToSleep" + "deviceNotSleepingOrNotStartingToSleep", + tableLogBuffer ), logAndObserve( combine( @@ -282,15 +306,18 @@ constructor( ) { a, b -> !a || b }, - "secureCameraNotActiveOrAltBouncerIsShowing" + "secureCameraNotActiveOrAltBouncerIsShowing", + tableLogBuffer ), logAndObserve( biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture, - "isFaceAuthSupportedInCurrentPosture" + "isFaceAuthSupportedInCurrentPosture", + tableLogBuffer ), logAndObserve( biometricSettingsRepository.isCurrentUserInLockdown.isFalse(), - "userHasNotLockedDownDevice" + "userHasNotLockedDownDevice", + tableLogBuffer ) ) .reduce(::and) @@ -299,20 +326,27 @@ constructor( private fun observeFaceAuthGatingChecks() { // Face auth can run only if all of the gating conditions are true. listOf( - canFaceAuthOrDetectRun(), - logAndObserve(isLockedOut.isFalse(), "isNotLocked"), + canFaceAuthOrDetectRun(faceAuthLog), + logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog), logAndObserve( deviceEntryFingerprintAuthRepository.isLockedOut.isFalse(), - "fpLockedOut" + "fpLockedOut", + faceAuthLog + ), + logAndObserve( + trustRepository.isCurrentUserTrusted.isFalse(), + "currentUserTrusted", + faceAuthLog ), - logAndObserve(trustRepository.isCurrentUserTrusted.isFalse(), "currentUserTrusted"), logAndObserve( biometricSettingsRepository.isNonStrongBiometricAllowed, - "nonStrongBiometricIsAllowed" + "nonStrongBiometricIsAllowed", + faceAuthLog ), logAndObserve( userRepository.selectedUserInfo.map { it.isPrimary }, - "userIsPrimaryUser" + "userIsPrimaryUser", + faceAuthLog ), ) .reduce(::and) @@ -326,6 +360,7 @@ constructor( cancel() } } + .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false) .launchIn(applicationScope) } @@ -340,7 +375,6 @@ constructor( override fun onAuthenticationAcquired(acquireInfo: Int) { _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo) - faceAuthLogger.authenticationAcquired(acquireInfo) } override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) { @@ -401,7 +435,7 @@ constructor( override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { if (_isAuthRunning.value) { - faceAuthLogger.ignoredFaceAuthTrigger(uiEvent) + faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running") return } @@ -438,7 +472,16 @@ constructor( ) } } else if (fallbackToDetection && canRunDetection.value) { + faceAuthLogger.ignoredFaceAuthTrigger( + uiEvent, + "face auth gating check is false, falling back to detection." + ) detect() + } else { + faceAuthLogger.ignoredFaceAuthTrigger( + uiEvent, + "face auth & detect gating check is false" + ) } } @@ -467,7 +510,7 @@ constructor( private val currentUserId: Int get() = userRepository.getSelectedUserInfo().id - fun cancelDetection() { + private fun cancelDetection() { detectCancellationSignal?.cancel() detectCancellationSignal = null } @@ -491,10 +534,20 @@ constructor( _isAuthRunning.value = false } - private fun logAndObserve(cond: Flow<Boolean>, loggingContext: String): Flow<Boolean> { - return cond.distinctUntilChanged().onEach { - faceAuthLogger.observedConditionChanged(it, loggingContext) - } + private fun logAndObserve( + cond: Flow<Boolean>, + conditionName: String, + logBuffer: TableLogBuffer + ): Flow<Boolean> { + return cond + .distinctUntilChanged() + .logDiffsForTable( + logBuffer, + columnName = conditionName, + columnPrefix = "", + initialValue = false + ) + .onEach { faceAuthLogger.observedConditionChanged(it, conditionName) } } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt new file mode 100644 index 000000000000..6c23032143c1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import javax.inject.Qualifier + +/** Face auth logs in table format. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class FaceAuthTableLog diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt new file mode 100644 index 000000000000..342064f02ef2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.repository + +import javax.inject.Qualifier + +/** Face detect logs in table format. */ +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class FaceDetectTableLog 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 index 3c66f2424c7b..ef8b40191149 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt @@ -17,8 +17,17 @@ package com.android.systemui.keyguard.data.repository +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor +import com.android.systemui.keyguard.domain.interactor.SystemUIKeyguardFaceAuthInteractor +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory import dagger.Binds import dagger.Module +import dagger.Provides +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap @Module interface KeyguardFaceAuthModule { @@ -27,5 +36,31 @@ interface KeyguardFaceAuthModule { impl: DeviceEntryFaceAuthRepositoryImpl ): DeviceEntryFaceAuthRepository + @Binds + @IntoMap + @ClassKey(SystemUIKeyguardFaceAuthInteractor::class) + fun bind(impl: SystemUIKeyguardFaceAuthInteractor): CoreStartable + + @Binds + fun keyguardFaceAuthInteractor( + impl: SystemUIKeyguardFaceAuthInteractor + ): KeyguardFaceAuthInteractor + @Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository + + companion object { + @Provides + @SysUISingleton + @FaceAuthTableLog + fun provideFaceAuthTableLog(factory: TableLogBufferFactory): TableLogBuffer { + return factory.create("FaceAuthTableLog", 100) + } + + @Provides + @SysUISingleton + @FaceDetectTableLog + fun provideFaceDetectTableLog(factory: TableLogBufferFactory): TableLogBuffer { + return factory.create("FaceDetectTableLog", 100) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt new file mode 100644 index 000000000000..06ae11fe810c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt @@ -0,0 +1,78 @@ +/* + * 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.domain.interactor + +import com.android.systemui.keyguard.shared.model.AuthenticationStatus +import com.android.systemui.keyguard.shared.model.DetectionStatus +import kotlinx.coroutines.flow.Flow + +/** + * Interactor that exposes API to get the face authentication status and handle any events that can + * cause face authentication to run. + */ +interface KeyguardFaceAuthInteractor { + + /** Current authentication status */ + val authenticationStatus: Flow<AuthenticationStatus> + + /** Current detection status */ + val detectionStatus: Flow<DetectionStatus> + + /** Can face auth be run right now */ + fun canFaceAuthRun(): Boolean + + /** Whether face auth is currently running or not. */ + fun isRunning(): Boolean + + /** Whether face auth is in lock out state. */ + fun isLockedOut(): Boolean + + /** + * Register listener for use from code that cannot use [authenticationStatus] or + * [detectionStatus] + */ + fun registerListener(listener: FaceAuthenticationListener) + + /** Unregister previously registered listener */ + fun unregisterListener(listener: FaceAuthenticationListener) + + /** Whether the face auth interactor is enabled or not. */ + fun isEnabled(): Boolean + + fun onUdfpsSensorTouched() + fun onAssistantTriggeredOnLockScreen() + fun onDeviceLifted() + fun onQsExpansionStared() + fun onNotificationPanelClicked() + fun onSwipeUpOnBouncer() +} + +/** + * Listener that can be registered with the [KeyguardFaceAuthInteractor] to receive updates about + * face authentication & detection updates. + * + * This is present to make it easier for use the new face auth API for code that cannot use + * [KeyguardFaceAuthInteractor.authenticationStatus] or [KeyguardFaceAuthInteractor.detectionStatus] + * flows. + */ +interface FaceAuthenticationListener { + /** Receive face authentication status updates */ + fun onAuthenticationStatusChanged(status: AuthenticationStatus) + + /** Receive status updates whenever face detection runs */ + fun onDetectionStatusChanged(status: DetectionStatus) +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt index aabd212c1bd3..da0ada160518 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt @@ -78,6 +78,14 @@ constructor( val primaryBouncerToGoneTransition: Flow<TransitionStep> = repository.transition(PRIMARY_BOUNCER, GONE) + /** OFF->LOCKSCREEN transition information. */ + val offToLockscreenTransition: Flow<TransitionStep> = + repository.transition(KeyguardState.OFF, LOCKSCREEN) + + /** DOZING->LOCKSCREEN transition information. */ + val dozingToLockscreenTransition: Flow<TransitionStep> = + repository.transition(KeyguardState.DOZING, LOCKSCREEN) + /** * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <-> * Lockscreen (0f). diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt new file mode 100644 index 000000000000..cad40aac00d3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.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.systemui.keyguard.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.AuthenticationStatus +import com.android.systemui.keyguard.shared.model.DetectionStatus +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +/** + * Implementation of the interactor that noops all face auth operations. + * + * This is required for SystemUI variants that do not support face authentication but still inject + * other SysUI components that depend on [KeyguardFaceAuthInteractor] + */ +@SysUISingleton +class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor { + override val authenticationStatus: Flow<AuthenticationStatus> + get() = emptyFlow() + override val detectionStatus: Flow<DetectionStatus> + get() = emptyFlow() + + override fun canFaceAuthRun(): Boolean = false + + override fun isRunning(): Boolean = false + + override fun isLockedOut(): Boolean = false + + override fun isEnabled() = false + + override fun registerListener(listener: FaceAuthenticationListener) {} + + override fun unregisterListener(listener: FaceAuthenticationListener) {} + + override fun onUdfpsSensorTouched() {} + + override fun onAssistantTriggeredOnLockScreen() {} + + override fun onDeviceLifted() {} + + override fun onQsExpansionStared() {} + + override fun onNotificationPanelClicked() {} + + override fun onSwipeUpOnBouncer() {} +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt new file mode 100644 index 000000000000..20ebb711c42d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -0,0 +1,200 @@ +/* + * 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.domain.interactor + +import com.android.keyguard.FaceAuthUiEvent +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.log.FaceAuthenticationLogger +import com.android.systemui.util.kotlin.pairwise +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +/** + * Encapsulates business logic related face authentication being triggered for device entry from + * SystemUI Keyguard. + */ +@SysUISingleton +class SystemUIKeyguardFaceAuthInteractor +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + @Main private val mainDispatcher: CoroutineDispatcher, + private val repository: DeviceEntryFaceAuthRepository, + private val primaryBouncerInteractor: PrimaryBouncerInteractor, + private val alternateBouncerInteractor: AlternateBouncerInteractor, + private val keyguardTransitionInteractor: KeyguardTransitionInteractor, + private val featureFlags: FeatureFlags, + private val faceAuthenticationLogger: FaceAuthenticationLogger, + private val keyguardUpdateMonitor: KeyguardUpdateMonitor, +) : CoreStartable, KeyguardFaceAuthInteractor { + + private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf() + + override fun start() { + if (!isEnabled()) { + return + } + // This is required because fingerprint state required for the face auth repository is + // backed by KeyguardUpdateMonitor. KeyguardUpdateMonitor constructor accesses the biometric + // state which makes lazy injection not an option. + keyguardUpdateMonitor.setFaceAuthInteractor(this) + observeFaceAuthStateUpdates() + faceAuthenticationLogger.interactorStarted() + primaryBouncerInteractor.isShowing + .whenItFlipsToTrue() + .onEach { + faceAuthenticationLogger.bouncerVisibilityChanged() + runFaceAuth( + FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, + fallbackToDetect = true + ) + } + .launchIn(applicationScope) + + alternateBouncerInteractor.isVisible + .whenItFlipsToTrue() + .onEach { + faceAuthenticationLogger.alternateBouncerVisibilityChanged() + runFaceAuth( + FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN, + fallbackToDetect = false + ) + } + .launchIn(applicationScope) + + merge( + keyguardTransitionInteractor.aodToLockscreenTransition, + keyguardTransitionInteractor.offToLockscreenTransition, + keyguardTransitionInteractor.dozingToLockscreenTransition + ) + .filter { it.transitionState == TransitionState.STARTED } + .onEach { + faceAuthenticationLogger.lockscreenBecameVisible(it) + runFaceAuth( + FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, + fallbackToDetect = true + ) + } + .launchIn(applicationScope) + } + + override fun onSwipeUpOnBouncer() { + runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false) + } + + override fun onNotificationPanelClicked() { + runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true) + } + + override fun onQsExpansionStared() { + runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true) + } + + override fun onDeviceLifted() { + runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true) + } + + override fun onAssistantTriggeredOnLockScreen() { + runFaceAuth(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true) + } + + override fun onUdfpsSensorTouched() { + runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false) + } + + override fun registerListener(listener: FaceAuthenticationListener) { + listeners.add(listener) + } + + override fun unregisterListener(listener: FaceAuthenticationListener) { + listeners.remove(listener) + } + + override fun isLockedOut(): Boolean = repository.isLockedOut.value + + override fun isRunning(): Boolean = repository.isAuthRunning.value + + override fun canFaceAuthRun(): Boolean = repository.canRunFaceAuth.value + + override fun isEnabled(): Boolean { + return featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR) + } + + /** Provide the status of face authentication */ + override val authenticationStatus = repository.authenticationStatus + + /** Provide the status of face detection */ + override val detectionStatus = repository.detectionStatus + + private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) { + if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) { + applicationScope.launch { + faceAuthenticationLogger.authRequested(uiEvent) + repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect) + } + } else { + faceAuthenticationLogger.ignoredFaceAuthTrigger( + uiEvent, + ignoredReason = "Skipping face auth request because feature flag is false" + ) + } + } + + private fun observeFaceAuthStateUpdates() { + authenticationStatus + .onEach { authStatusUpdate -> + listeners.forEach { it.onAuthenticationStatusChanged(authStatusUpdate) } + } + .flowOn(mainDispatcher) + .launchIn(applicationScope) + detectionStatus + .onEach { detectionStatusUpdate -> + listeners.forEach { it.onDetectionStatusChanged(detectionStatusUpdate) } + } + .flowOn(mainDispatcher) + .launchIn(applicationScope) + } + + companion object { + const val TAG = "KeyguardFaceAuthInteractor" + } +} + +// Extension method that filters a generic Boolean flow to one that emits +// whenever there is flip from false -> true +private fun Flow<Boolean>.whenItFlipsToTrue(): Flow<Boolean> { + return this.pairwise() + .filter { pair -> !pair.previousValue && pair.newValue } + .map { it.newValue } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/TrustAgentUiEvent.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/TrustAgentUiEvent.kt new file mode 100644 index 000000000000..ef6079fbc25b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/constants/TrustAgentUiEvent.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.shared.constants + +import com.android.internal.logging.UiEvent +import com.android.internal.logging.UiEventLogger + +enum class TrustAgentUiEvent(private val metricId: Int) : UiEventLogger.UiEventEnum { + @UiEvent(doc = "TrustAgent newly unlocked the device") TRUST_AGENT_NEWLY_UNLOCKED(1361); + override fun getId() = metricId +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index 68ac7e1c71e5..d96609c24dbd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -498,7 +498,7 @@ object KeyguardBottomAreaViewBinder { activityStarter: ActivityStarter, view: View, ) { - activityStarter.startActivity( + activityStarter.postStartActivityDismissingKeyguard( Intent(Intent.ACTION_SET_WALLPAPER).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK view.context @@ -506,8 +506,9 @@ object KeyguardBottomAreaViewBinder { .takeIf { it.isNotEmpty() } ?.let { packageName -> setPackage(packageName) } }, - /* dismissShade= */ true, - ActivityLaunchAnimator.Controller.fromView(view), + /* delay= */ 0, + /* animationController= */ ActivityLaunchAnimator.Controller.fromView(view), + /* customMessage= */ view.context.getString(R.string.keyguard_unlock_to_customize_ls) ) } 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 72dc7a4f7753..6bbc6f61cc6f 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 @@ -193,7 +193,11 @@ object KeyguardBouncerViewBinder { launch { viewModel.bouncerShowMessage.collect { - securityContainerController.showMessage(it.message, it.colorStateList) + securityContainerController.showMessage( + it.message, + it.colorStateList, + /* animated= */ true + ) viewModel.onMessageShown() } } diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt index f7355d5c11e2..7f6e4a903d76 100644 --- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt @@ -4,6 +4,7 @@ import android.hardware.face.FaceManager import android.hardware.face.FaceSensorPropertiesInternal import com.android.keyguard.FaceAuthUiEvent import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.log.dagger.FaceAuthLog import com.android.systemui.plugins.log.LogBuffer import com.android.systemui.plugins.log.LogLevel.DEBUG @@ -27,15 +28,15 @@ class FaceAuthenticationLogger constructor( @FaceAuthLog private val logBuffer: LogBuffer, ) { - fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent) { + fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent, ignoredReason: String) { logBuffer.log( TAG, DEBUG, - { str1 = uiEvent.reason }, { - "Ignoring trigger because face auth is currently running. " + - "Trigger reason: $str1" - } + str1 = uiEvent.reason + str2 = ignoredReason + }, + { "Ignoring trigger because $str2, Trigger reason: $str1" } ) } @@ -135,15 +136,6 @@ constructor( logBuffer.log(TAG, DEBUG, "Face authentication failed") } - fun authenticationAcquired(acquireInfo: Int) { - logBuffer.log( - TAG, - DEBUG, - { int1 = acquireInfo }, - { "Face acquired during face authentication: acquireInfo: $int1 " } - ) - } - fun authenticationError( errorCode: Int, errString: CharSequence?, @@ -217,4 +209,34 @@ constructor( fun cancellingFaceAuth() { logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false") } + + fun interactorStarted() { + logBuffer.log(TAG, DEBUG, "KeyguardFaceAuthInteractor started") + } + + fun bouncerVisibilityChanged() { + logBuffer.log(TAG, DEBUG, "Triggering face auth because primary bouncer is visible") + } + + fun alternateBouncerVisibilityChanged() { + logBuffer.log(TAG, DEBUG, "Triggering face auth because alternate bouncer is visible") + } + + fun lockscreenBecameVisible(transitionStep: TransitionStep?) { + logBuffer.log( + TAG, + DEBUG, + { str1 = "$transitionStep" }, + { "Triggering face auth because lockscreen became visible due to transition: $str1" } + ) + } + + fun authRequested(uiEvent: FaceAuthUiEvent) { + logBuffer.log( + TAG, + DEBUG, + { str1 = "$uiEvent" }, + { "Requesting face auth for trigger: $str1" } + ) + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt index 4cc041074a8d..fa4211487d1d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaDataManager.kt @@ -49,6 +49,7 @@ import android.service.notification.StatusBarNotification import android.support.v4.media.MediaMetadataCompat import android.text.TextUtils import android.util.Log +import android.util.Pair as APair import androidx.media.utils.MediaConstants import com.android.internal.logging.InstanceId import com.android.keyguard.KeyguardUpdateMonitor @@ -217,6 +218,13 @@ class MediaDataManager( private var smartspaceSession: SmartspaceSession? = null private var allowMediaRecommendations = allowMediaRecommendations(context) + private val artworkWidth = + context.resources.getDimensionPixelSize( + com.android.internal.R.dimen.config_mediaMetadataBitmapMaxSize + ) + private val artworkHeight = + context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded) + /** Check whether this notification is an RCN */ private fun isRemoteCastNotification(sbn: StatusBarNotification): Boolean { return sbn.notification.extras.containsKey(Notification.EXTRA_MEDIA_REMOTE_DEVICE) @@ -1250,9 +1258,21 @@ class MediaDataManager( return null } - val source = ImageDecoder.createSource(context.getContentResolver(), uri) + val source = ImageDecoder.createSource(context.contentResolver, uri) return try { - ImageDecoder.decodeBitmap(source) { decoder, _, _ -> + ImageDecoder.decodeBitmap(source) { decoder, info, _ -> + val width = info.size.width + val height = info.size.height + val scale = + MediaDataUtils.getScaleFactor( + APair(width, height), + APair(artworkWidth, artworkHeight) + ) + + // Downscale if needed + if (scale != 0f && scale < 1) { + decoder.setTargetSize((scale * width).toInt(), (scale * height).toInt()) + } decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE } } catch (e: IOException) { diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt index a1d9214cb215..ed4eef9eaa2b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/MediaTimeoutListener.kt @@ -17,6 +17,7 @@ package com.android.systemui.media.controls.pipeline import android.media.session.MediaController +import android.media.session.MediaSession import android.media.session.PlaybackState import android.os.SystemProperties import com.android.internal.annotations.VisibleForTesting @@ -148,7 +149,7 @@ constructor( reusedListener?.let { val wasPlaying = it.isPlaying() logger.logUpdateListener(key, wasPlaying) - it.mediaData = data + it.setMediaData(data) it.key = key mediaListeners[key] = it if (wasPlaying != it.isPlaying()) { @@ -208,24 +209,7 @@ constructor( var resumption: Boolean? = null var destroyed = false var expiration = Long.MAX_VALUE - - var mediaData: MediaData = data - set(value) { - destroyed = false - mediaController?.unregisterCallback(this) - field = value - val token = field.token - mediaController = - if (token != null) { - mediaControllerFactory.create(token) - } else { - null - } - mediaController?.registerCallback(this) - // Let's register the cancellations, but not dispatch events now. - // Timeouts didn't happen yet and reentrant events are troublesome. - processState(mediaController?.playbackState, dispatchEvents = false) - } + var sessionToken: MediaSession.Token? = null // Resume controls may have null token private var mediaController: MediaController? = null @@ -236,7 +220,7 @@ constructor( fun isPlaying() = lastState?.state?.isPlaying() ?: false init { - mediaData = data + setMediaData(data) } fun destroy() { @@ -245,8 +229,28 @@ constructor( destroyed = true } + fun setMediaData(data: MediaData) { + sessionToken = data.token + destroyed = false + mediaController?.unregisterCallback(this) + mediaController = + if (data.token != null) { + mediaControllerFactory.create(data.token) + } else { + null + } + mediaController?.registerCallback(this) + // Let's register the cancellations, but not dispatch events now. + // Timeouts didn't happen yet and reentrant events are troublesome. + processState( + mediaController?.playbackState, + dispatchEvents = false, + currentResumption = data.resumption, + ) + } + override fun onPlaybackStateChanged(state: PlaybackState?) { - processState(state, dispatchEvents = true) + processState(state, dispatchEvents = true, currentResumption = resumption) } override fun onSessionDestroyed() { @@ -263,14 +267,18 @@ constructor( } } - private fun processState(state: PlaybackState?, dispatchEvents: Boolean) { + private fun processState( + state: PlaybackState?, + dispatchEvents: Boolean, + currentResumption: Boolean?, + ) { logger.logPlaybackState(key, state) val playingStateSame = (state?.state?.isPlaying() == isPlaying()) val actionsSame = (lastState?.actions == state?.actions) && areCustomActionListsEqual(lastState?.customActions, state?.customActions) - val resumptionChanged = resumption != mediaData.resumption + val resumptionChanged = resumption != currentResumption lastState = state @@ -282,7 +290,7 @@ constructor( if (playingStateSame && !resumptionChanged) { return } - resumption = mediaData.resumption + resumption = currentResumption val playing = isPlaying() if (!playing) { @@ -294,7 +302,7 @@ constructor( } expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state, $resumption") val timeout = - if (mediaData.resumption) { + if (currentResumption == true) { RESUME_MEDIA_TIMEOUT } else { PAUSED_MEDIA_TIMEOUT diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java index cb1f12cf412f..40027a1d8f2c 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java @@ -56,6 +56,7 @@ import android.os.Process; import android.os.Trace; import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import android.util.TypedValue; import android.view.Gravity; import android.view.View; @@ -122,6 +123,11 @@ import com.android.systemui.util.animation.TransitionLayout; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.time.SystemClock; +import dagger.Lazy; + +import kotlin.Triple; +import kotlin.Unit; + import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; @@ -129,10 +135,6 @@ import java.util.concurrent.Executor; import javax.inject.Inject; -import dagger.Lazy; -import kotlin.Triple; -import kotlin.Unit; - /** * A view controller used for Media Playback. */ @@ -1000,18 +1002,9 @@ public class MediaControlPanel { int width = drawable.getIntrinsicWidth(); int height = drawable.getIntrinsicHeight(); - if (width == 0 || height == 0 || targetWidth == 0 || targetHeight == 0) { - return; - } - - float scale; - if ((width / (float) height) > (targetWidth / (float) targetHeight)) { - // Drawable is wider than target view, scale to match height - scale = targetHeight / (float) height; - } else { - // Drawable is taller than target view, scale to match width - scale = targetWidth / (float) width; - } + float scale = MediaDataUtils.getScaleFactor(new Pair(width, height), + new Pair(targetWidth, targetHeight)); + if (scale == 0) return; transitionDrawable.setLayerSize(layer, (int) (scale * width), (int) (scale * height)); } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java index e95106e0987a..0239d367e52d 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaDataUtils.java @@ -22,6 +22,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Bundle; import android.text.TextUtils; +import android.util.Pair; import androidx.core.math.MathUtils; import androidx.media.utils.MediaConstants; @@ -87,4 +88,32 @@ public class MediaDataUtils { } return null; } + + /** + * Calculate a scale factor that will allow the input to fill the target size. + * + * @param input width, height of the input view + * @param target width, height of the target view + * @return the scale factor; 0 if any given dimension is 0 + */ + public static float getScaleFactor(Pair<Integer, Integer> input, + Pair<Integer, Integer> target) { + float width = (float) input.first; + float height = (float) input.second; + + float targetWidth = (float) target.first; + float targetHeight = (float) target.second; + + if (width == 0 || height == 0 || targetWidth == 0 || targetHeight == 0) { + return 0f; + } + + if ((width / height) > (targetWidth / targetHeight)) { + // Input is wider than target view, scale to match height + return targetHeight / height; + } else { + // Input is taller than target view, scale to match width + return targetWidth / width; + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 9606bcf3fd9b..08e47a09bab9 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -273,8 +273,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } @Override - public void onStart() { - super.onStart(); + public void start() { mMediaOutputController.start(this); if (isBroadcastSupported() && !mIsLeBroadcastCallbackRegistered) { mMediaOutputController.registerLeBroadcastServiceCallback(mExecutor, @@ -284,8 +283,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements } @Override - public void onStop() { - super.onStop(); + public void stop() { if (isBroadcastSupported() && mIsLeBroadcastCallbackRegistered) { mMediaOutputController.unregisterLeBroadcastServiceCallback(mBroadcastCallback); mIsLeBroadcastCallbackRegistered = false; diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java index f0ff1409faf1..abf0932c8407 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java @@ -212,8 +212,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } @Override - public void onStart() { - super.onStart(); + public void start() { + super.start(); if (!mIsLeBroadcastAssistantCallbackRegistered) { mIsLeBroadcastAssistantCallbackRegistered = true; mMediaOutputController.registerLeBroadcastAssistantServiceCallback(mExecutor, @@ -223,8 +223,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog { } @Override - public void onStop() { - super.onStop(); + public void stop() { + super.stop(); if (mIsLeBroadcastAssistantCallbackRegistered) { mIsLeBroadcastAssistantCallbackRegistered = false; mMediaOutputController.unregisterLeBroadcastAssistantServiceCallback( diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 334c70b217a3..5f4e7cac4452 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -34,7 +34,9 @@ import android.os.Build import android.os.UserHandle import android.os.UserManager import android.util.Log +import android.widget.Toast import androidx.annotation.VisibleForTesting +import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser @@ -170,7 +172,13 @@ constructor( return } - val info = resolver.resolveInfo(entryPoint, isKeyguardLocked) ?: return + val info = resolver.resolveInfo(entryPoint, isKeyguardLocked) + + if (info == null) { + logDebug { "Default notes app isn't set" } + showNoDefaultNotesAppToast() + return + } infoReference.set(info) @@ -207,6 +215,12 @@ constructor( logDebug { "onShowNoteTask - completed: $info" } } + @VisibleForTesting + fun showNoDefaultNotesAppToast() { + Toast.makeText(context, R.string.set_default_notes_app_toast_content, Toast.LENGTH_SHORT) + .show() + } + /** * Set `android:enabled` property in the `AndroidManifest` associated with the Shortcut * component to [value]. @@ -214,7 +228,7 @@ constructor( * If the shortcut entry `android:enabled` is set to `true`, the shortcut will be visible in the * Widget Picker to all users. */ - fun setNoteTaskShortcutEnabled(value: Boolean) { + fun setNoteTaskShortcutEnabled(value: Boolean, user: UserHandle) { val componentName = ComponentName(context, CreateNoteTaskShortcutActivity::class.java) val enabledState = @@ -224,7 +238,16 @@ constructor( PackageManager.COMPONENT_ENABLED_STATE_DISABLED } - context.packageManager.setComponentEnabledSetting( + // If the required user matches the tracking user, the injected context is already a context + // of the required user. Avoid calling #createContextAsUser because creating a context for + // a user takes time. + val userContext = + if (user == userTracker.userHandle) { + context + } else { + context.createContextAsUser(user, /* flags= */ 0) + } + userContext.packageManager.setComponentEnabledSetting( componentName, enabledState, PackageManager.DONT_KILL_APP, @@ -246,7 +269,7 @@ constructor( val packageName = roleManager.getDefaultRoleHolderAsUser(ROLE_NOTES, user) val hasNotesRoleHolder = isEnabled && !packageName.isNullOrEmpty() - setNoteTaskShortcutEnabled(hasNotesRoleHolder) + setNoteTaskShortcutEnabled(hasNotesRoleHolder, user) if (hasNotesRoleHolder) { shortcutManager.enableShortcuts(listOf(SHORTCUT_ID)) diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index 23ee13b4deac..7bb615b8d866 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -20,6 +20,7 @@ import android.os.UserHandle import android.view.KeyEvent import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.CommandQueue import com.android.wm.shell.bubbles.Bubbles import java.util.Optional @@ -36,6 +37,7 @@ constructor( private val optionalBubbles: Optional<Bubbles>, @Background private val backgroundExecutor: Executor, @NoteTaskEnabledKey private val isEnabled: Boolean, + private val userTracker: UserTracker, ) { @VisibleForTesting @@ -44,8 +46,9 @@ constructor( 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) { + } else if ( + key.keyCode == KeyEvent.KEYCODE_N && key.isMetaPressed && key.isCtrlPressed + ) { controller.showNoteTask(NoteTaskEntryPoint.KEYBOARD_SHORTCUT) } } @@ -55,7 +58,7 @@ constructor( // Guard against feature not being enabled or mandatory dependencies aren't available. if (!isEnabled || optionalBubbles.isEmpty) return - controller.setNoteTaskShortcutEnabled(true) + controller.setNoteTaskShortcutEnabled(true, userTracker.userHandle) commandQueue.addCallback(callbacks) roleManager.addOnRoleHoldersChangedListenerAsUser( backgroundExecutor, diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt index 03145a714289..35a7cf1bf402 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt @@ -90,8 +90,7 @@ class PrivacyDialog( } } - override fun onStop() { - super.onStop() + override fun stop() { dismissed.set(true) val iterator = dismissListeners.iterator() while (iterator.hasNext()) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt new file mode 100644 index 000000000000..99792286d962 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSAutoAddModule.kt @@ -0,0 +1,28 @@ +/* + * 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.dagger + +import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository +import com.android.systemui.qs.pipeline.data.repository.AutoAddSettingRepository +import dagger.Binds +import dagger.Module + +@Module +abstract class QSAutoAddModule { + + @Binds abstract fun bindAutoAddRepository(impl: AutoAddSettingRepository): AutoAddRepository +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt index e212bc4e7f5d..e85440cad6b0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt @@ -32,7 +32,7 @@ import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap -@Module +@Module(includes = [QSAutoAddModule::class]) abstract class QSPipelineModule { /** Implementation for [TileSpecRepository] */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt new file mode 100644 index 000000000000..43a16b69d1a8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt @@ -0,0 +1,135 @@ +/* + * 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.database.ContentObserver +import android.provider.Settings +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.util.settings.SecureSettings +import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.withContext + +/** Repository to track what QS tiles have been auto-added */ +interface AutoAddRepository { + + /** Flow of tiles that have been auto-added */ + fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> + + /** Mark a tile as having been auto-added */ + suspend fun markTileAdded(userId: Int, spec: TileSpec) + + /** + * Unmark a tile as having been auto-added. This is used for tiles that can be auto-added + * multiple times. + */ + suspend fun unmarkTileAdded(userId: Int, spec: TileSpec) +} + +/** + * Implementation that tracks the auto-added tiles stored in [Settings.Secure.QS_AUTO_ADDED_TILES]. + */ +@SysUISingleton +class AutoAddSettingRepository +@Inject +constructor( + private val secureSettings: SecureSettings, + @Background private val bgDispatcher: CoroutineDispatcher, +) : AutoAddRepository { + override fun autoAddedTiles(userId: Int): Flow<Set<TileSpec>> { + return conflatedCallbackFlow { + val observer = + object : ContentObserver(null) { + override fun onChange(selfChange: Boolean) { + trySend(Unit) + } + } + + secureSettings.registerContentObserverForUser(SETTING, observer, userId) + + awaitClose { secureSettings.unregisterContentObserver(observer) } + } + .onStart { emit(Unit) } + .map { secureSettings.getStringForUser(SETTING, userId) ?: "" } + .distinctUntilChanged() + .map { + it.split(DELIMITER).map(TileSpec::create).filter { it !is TileSpec.Invalid }.toSet() + } + .flowOn(bgDispatcher) + } + + override suspend fun markTileAdded(userId: Int, spec: TileSpec) { + if (spec is TileSpec.Invalid) { + return + } + val added = load(userId).toMutableSet() + if (added.add(spec)) { + store(userId, added) + } + } + + override suspend fun unmarkTileAdded(userId: Int, spec: TileSpec) { + if (spec is TileSpec.Invalid) { + return + } + val added = load(userId).toMutableSet() + if (added.remove(spec)) { + store(userId, added) + } + } + + private suspend fun store(userId: Int, tiles: Set<TileSpec>) { + val toStore = + tiles + .filter { it !is TileSpec.Invalid } + .joinToString(DELIMITER, transform = TileSpec::spec) + withContext(bgDispatcher) { + secureSettings.putStringForUser( + SETTING, + toStore, + null, + false, + userId, + true, + ) + } + } + + private suspend fun load(userId: Int): Set<TileSpec> { + return withContext(bgDispatcher) { + (secureSettings.getStringForUser(SETTING, userId) ?: "") + .split(",") + .map(TileSpec::create) + .filter { it !is TileSpec.Invalid } + .toSet() + } + } + + companion object { + private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES + private const val DELIMITER = "," + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt index 89408006c300..bbd72341acc6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/prototyping/PrototypeCoreStartable.kt @@ -16,11 +16,13 @@ package com.android.systemui.qs.pipeline.prototyping +import android.util.Log import com.android.systemui.CoreStartable 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.qs.pipeline.data.repository.AutoAddRepository import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.statusbar.commandline.Command @@ -46,6 +48,7 @@ class PrototypeCoreStartable @Inject constructor( private val tileSpecRepository: TileSpecRepository, + private val autoAddRepository: AutoAddRepository, private val userRepository: UserRepository, private val featureFlags: FeatureFlags, @Application private val scope: CoroutineScope, @@ -60,6 +63,13 @@ constructor( .flatMapLatest { user -> tileSpecRepository.tilesSpecs(user.id) } .collect {} } + if (featureFlags.isEnabled(Flags.QS_PIPELINE_AUTO_ADD)) { + scope.launch { + userRepository.selectedUserInfo + .flatMapLatest { user -> autoAddRepository.autoAddedTiles(user.id) } + .collect { tiles -> Log.d(TAG, "Auto-added tiles: $tiles") } + } + } commandRegistry.registerCommand(COMMAND, ::CommandExecutor) } } @@ -105,5 +115,6 @@ constructor( companion object { private const val COMMAND = "qs-pipeline" + private const val TAG = "PrototypeCoreStartable" } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java index 039dafb80522..380b85cb5504 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialog.java @@ -259,8 +259,7 @@ public class InternetDialog extends SystemUIDialog implements } @Override - public void onStart() { - super.onStart(); + public void start() { if (DEBUG) { Log.d(TAG, "onStart"); } @@ -280,8 +279,7 @@ public class InternetDialog extends SystemUIDialog implements } @Override - public void onStop() { - super.onStop(); + public void stop() { if (DEBUG) { Log.d(TAG, "onStop"); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index cffe45fadaa3..0a188e0e7d0b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -16,6 +16,7 @@ package com.android.systemui.recents; +import static android.content.Intent.ACTION_PACKAGE_ADDED; import static android.content.Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST; import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY; import static android.view.MotionEvent.ACTION_CANCEL; @@ -48,6 +49,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.pm.ResolveInfo; import android.graphics.Region; import android.hardware.input.InputManager; import android.hardware.input.InputManagerGlobal; @@ -114,6 +116,7 @@ import dagger.Lazy; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; @@ -393,20 +396,29 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private final BroadcastReceiver mLauncherStateChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - StringBuilder extraComponentList = new StringBuilder(" components: "); - if (intent.hasExtra(EXTRA_CHANGED_COMPONENT_NAME_LIST)) { - String[] comps = intent.getStringArrayExtra(EXTRA_CHANGED_COMPONENT_NAME_LIST); - if (comps != null) { - for (String c : comps) { - extraComponentList.append(c).append(", "); - } - } + // If adding, bind immediately + if (Objects.equals(intent.getAction(), ACTION_PACKAGE_ADDED)) { + updateEnabledAndBinding(); + return; + } + + // ACTION_PACKAGE_CHANGED + String[] compsList = intent.getStringArrayExtra(EXTRA_CHANGED_COMPONENT_NAME_LIST); + if (compsList == null) { + return; } - Log.d(TAG_OPS, "launcherStateChanged intent: " + intent + extraComponentList); - updateEnabledState(); - // Reconnect immediately, instead of waiting for resume to arrive. - startConnectionToCurrentUser(); + // Only rebind for TouchInteractionService component from launcher + ResolveInfo ri = context.getPackageManager() + .resolveService(new Intent(ACTION_QUICKSTEP), 0); + String interestingComponent = ri.serviceInfo.name; + for (String component : compsList) { + if (interestingComponent.equals(component)) { + Log.i(TAG_OPS, "Rebinding for component [" + component + "] change"); + updateEnabledAndBinding(); + return; + } + } } }; @@ -621,8 +633,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis screenLifecycle.addObserver(mScreenLifecycleObserver); wakefulnessLifecycle.addObserver(mWakefulnessLifecycleObserver); // Connect to the service - updateEnabledState(); - startConnectionToCurrentUser(); + updateEnabledAndBinding(); // Listen for assistant changes assistUtils.registerVoiceInteractionSessionListener(mVoiceInteractionSessionListener); @@ -644,6 +655,10 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis private void dispatchNavigationBarSurface() { try { if (mOverviewProxy != null) { + // Catch all for cases where the surface is no longer valid + if (mNavigationBarSurface != null && !mNavigationBarSurface.isValid()) { + mNavigationBarSurface = null; + } mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface); } } catch (RemoteException e) { @@ -651,6 +666,11 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + private void updateEnabledAndBinding() { + updateEnabledState(); + startConnectionToCurrentUser(); + } + private void updateSystemUiStateFlags() { final NavigationBar navBarFragment = mNavBarControllerLazy.get().getDefaultNavigationBar(); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 222a0f4fa248..a4a7d4cd76e5 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -129,6 +129,7 @@ import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants; @@ -330,6 +331,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump private final PulseExpansionHandler mPulseExpansionHandler; private final KeyguardBypassController mKeyguardBypassController; private final KeyguardUpdateMonitor mUpdateMonitor; + private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; private final ConversationNotificationManager mConversationNotificationManager; private final AuthController mAuthController; private final MediaHierarchyManager mMediaHierarchyManager; @@ -747,7 +749,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump DumpManager dumpManager, KeyguardLongPressViewModel keyguardLongPressViewModel, KeyguardInteractor keyguardInteractor, - ActivityStarter activityStarter) { + ActivityStarter activityStarter, + KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) { mInteractionJankMonitor = interactionJankMonitor; keyguardStateController.addCallback(new KeyguardStateController.Callback() { @Override @@ -891,6 +894,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mScreenOffAnimationController = screenOffAnimationController; mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController; mLastDownEvents = new NPVCDownEventState.Buffer(MAX_DOWN_EVENT_BUFFER_SIZE); + mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; int currentMode = navigationModeController.addListener( mode -> mIsGestureNavigation = QuickStepContract.isGesturalMode(mode)); @@ -2764,6 +2768,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false"); // Try triggering face auth, this "might" run. Check // KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run. + mKeyguardFaceAuthInteractor.onNotificationPanelClicked(); boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth( FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED); diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index 7cb1cbe77539..ef14d1cb7f63 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -64,6 +64,7 @@ import com.android.systemui.classifier.Classifier; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; @@ -132,6 +133,7 @@ public class QuickSettingsController { private final FalsingCollector mFalsingCollector; private final LockscreenGestureLogger mLockscreenGestureLogger; private final ShadeLogger mShadeLog; + private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; private final FeatureFlags mFeatureFlags; private final InteractionJankMonitor mInteractionJankMonitor; private final FalsingManager mFalsingManager; @@ -318,7 +320,8 @@ public class QuickSettingsController { MetricsLogger metricsLogger, FeatureFlags featureFlags, InteractionJankMonitor interactionJankMonitor, - ShadeLogger shadeLog + ShadeLogger shadeLog, + KeyguardFaceAuthInteractor keyguardFaceAuthInteractor ) { mPanelViewControllerLazy = panelViewControllerLazy; mPanelView = panelView; @@ -357,6 +360,7 @@ public class QuickSettingsController { mLockscreenGestureLogger = lockscreenGestureLogger; mMetricsLogger = metricsLogger; mShadeLog = shadeLog; + mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; mFeatureFlags = featureFlags; mInteractionJankMonitor = interactionJankMonitor; @@ -937,6 +941,7 @@ public class QuickSettingsController { // When expanding QS, let's authenticate the user if possible, // this will speed up notification actions. if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) { + mKeyguardFaceAuthInteractor.onQsExpansionStared(); mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED); } } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt index b4653bef766d..f0815e93dccd 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeHeaderController.kt @@ -19,11 +19,14 @@ package com.android.systemui.shade import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.annotation.IdRes +import android.app.PendingIntent import android.app.StatusBarManager +import android.content.Intent import android.content.res.Configuration import android.os.Bundle import android.os.Trace import android.os.Trace.TRACE_TAG_APP +import android.provider.AlarmClock import android.util.Pair import android.view.DisplayCutout import android.view.View @@ -41,6 +44,7 @@ import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.ChipVisibilityListener import com.android.systemui.qs.HeaderPrivacyIconsController import com.android.systemui.shade.ShadeHeaderController.Companion.HEADER_TRANSITION_ID @@ -58,6 +62,7 @@ import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.Cent import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.SHADE_HEADER import com.android.systemui.statusbar.policy.Clock import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.NextAlarmController import com.android.systemui.statusbar.policy.VariableDateView import com.android.systemui.statusbar.policy.VariableDateViewController import com.android.systemui.util.ViewController @@ -91,6 +96,8 @@ constructor( private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager, private val demoModeController: DemoModeController, private val qsBatteryModeController: QsBatteryModeController, + private val nextAlarmController: NextAlarmController, + private val activityStarter: ActivityStarter, ) : ViewController<View>(header), Dumpable { companion object { @@ -103,6 +110,8 @@ constructor( @VisibleForTesting internal val LARGE_SCREEN_HEADER_CONSTRAINT = R.id.large_screen_header_constraint + @VisibleForTesting internal val DEFAULT_CLOCK_INTENT = Intent(AlarmClock.ACTION_SHOW_ALARMS) + private fun Int.stateToString() = when (this) { QQS_HEADER_CONSTRAINT -> "QQS Header" @@ -125,6 +134,7 @@ constructor( private var roundedCorners = 0 private var cutout: DisplayCutout? = null private var lastInsets: WindowInsets? = null + private var nextAlarmIntent: PendingIntent? = null private var qsDisabled = false private var visible = false @@ -252,6 +262,11 @@ constructor( } } + private val nextAlarmCallback = + NextAlarmController.NextAlarmChangeCallback { nextAlarm -> + nextAlarmIntent = nextAlarm?.showIntent + } + override fun onInit() { variableDateViewControllerFactory.create(date as VariableDateView).init() batteryMeterViewController.init() @@ -286,19 +301,23 @@ constructor( mShadeCarrierGroup.setPaddingRelative((v.width * v.scaleX).toInt(), 0, 0, 0) } + clock.setOnClickListener { launchClockActivity() } dumpManager.registerDumpable(this) configurationController.addCallback(configurationControllerListener) demoModeController.addCallback(demoModeReceiver) statusBarIconController.addIconGroup(iconManager) + nextAlarmController.addCallback(nextAlarmCallback) } override fun onViewDetached() { + clock.setOnClickListener(null) privacyIconsController.chipVisibilityListener = null dumpManager.unregisterDumpable(this::class.java.simpleName) configurationController.removeCallback(configurationControllerListener) demoModeController.removeCallback(demoModeReceiver) statusBarIconController.removeIconGroup(iconManager) + nextAlarmController.removeCallback(nextAlarmCallback) } fun disable(state1: Int, state2: Int, animate: Boolean) { @@ -318,6 +337,15 @@ constructor( .start() } + @VisibleForTesting + internal fun launchClockActivity() { + if (nextAlarmIntent != null) { + activityStarter.postStartActivityDismissingKeyguard(nextAlarmIntent) + } else { + activityStarter.postStartActivityDismissingKeyguard(DEFAULT_CLOCK_INTENT, 0 /*delay */) + } + } + private fun loadConstraints() { // Use resources.getXml instead of passing the resource id due to bug b/205018300 header diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java index ced725e0b1d6..ea9817c68c30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java @@ -702,6 +702,13 @@ public class NotificationMediaManager implements Dumpable { mProcessArtworkTasks.remove(task); } + // TODO(b/273443374): remove + public boolean isLockscreenWallpaperOnNotificationShade() { + return mBackdrop != null && mLockscreenWallpaper != null + && !mLockscreenWallpaper.isLockscreenLiveWallpaperEnabled() + && (mBackdropFront.isVisibleToUser() || mBackdropBack.isVisibleToUser()); + } + /** * {@link AsyncTask} to prepare album art for use as backdrop on lock screen. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java index ecd0c41ff82d..fcff4376811e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/AnimatableProperty.java @@ -48,6 +48,10 @@ public abstract class AnimatableProperty { View.SCALE_Y, R.id.scale_y_animator_tag, R.id.scale_y_animator_start_value_tag, R.id.scale_y_animator_end_value_tag); + public static final AnimatableProperty ALPHA = AnimatableProperty.from( + View.ALPHA, R.id.alpha_animator_tag, R.id.alpha_animator_start_value_tag, + R.id.alpha_animator_end_value_tag); + /** * Similar to X, however this doesn't allow for any other modifications other than from this * property. When using X, it's possible that the view is laid out during the animation, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 82bd45ce2279..6322edf5a1b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -24,6 +24,10 @@ import androidx.annotation.VisibleForTesting import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.expansionChanges @@ -40,22 +44,26 @@ import com.android.systemui.statusbar.policy.HeadsUpManager import com.android.systemui.statusbar.policy.headsUpEvents import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SettingsProxyExt.observerFlow -import javax.inject.Inject -import kotlin.time.Duration -import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.conflate +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.launch +import kotlinx.coroutines.yield +import javax.inject.Inject +import kotlin.time.Duration +import kotlin.time.Duration.Companion.seconds /** * Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section @@ -69,6 +77,7 @@ constructor( private val headsUpManager: HeadsUpManager, private val keyguardNotificationVisibilityProvider: KeyguardNotificationVisibilityProvider, private val keyguardRepository: KeyguardRepository, + private val keyguardTransitionRepository: KeyguardTransitionRepository, private val notifPipelineFlags: NotifPipelineFlags, @Application private val scope: CoroutineScope, private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider, @@ -99,21 +108,46 @@ constructor( } private suspend fun trackUnseenNotificationsWhileUnlocked() { + // Whether or not we're actively tracking unseen notifications to mark them as seen when + // appropriate. + val isTrackingUnseen: Flow<Boolean> = + keyguardRepository.isKeyguardShowing + // transformLatest so that we can cancel listening to keyguard transitions once + // isKeyguardShowing changes (after a successful transition to the keyguard). + .transformLatest { isShowing -> + if (isShowing) { + // If the keyguard is showing, we're not tracking unseen. + emit(false) + } else { + // If the keyguard stops showing, then start tracking unseen notifications. + emit(true) + // If the screen is turning off, stop tracking, but if that transition is + // cancelled, then start again. + emitAll( + keyguardTransitionRepository.transitions + .map { step -> !step.isScreenTurningOff } + ) + } + } + // Prevent double emit of `false` caused by transition to AOD, followed by keyguard + // showing + .distinctUntilChanged() + // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is // showing again - var clearUnseenOnUnlock = false - keyguardRepository.isKeyguardShowing.collectLatest { isKeyguardShowing -> - if (isKeyguardShowing) { + var clearUnseenOnBeginTracking = false + isTrackingUnseen.collectLatest { trackingUnseen -> + if (!trackingUnseen) { // Wait for the user to spend enough time on the lock screen before clearing unseen // set when unlocked awaitTimeSpentNotDozing(SEEN_TIMEOUT) - clearUnseenOnUnlock = true + clearUnseenOnBeginTracking = true } else { - unseenNotifFilter.invalidateList("keyguard no longer showing") - if (clearUnseenOnUnlock) { - clearUnseenOnUnlock = false + if (clearUnseenOnBeginTracking) { + clearUnseenOnBeginTracking = false unseenNotifications.clear() } + unseenNotifFilter.invalidateList("keyguard no longer showing") trackUnseenNotifications() } } @@ -142,7 +176,10 @@ constructor( } private suspend fun clearUnseenNotificationsWhenShadeIsExpanded() { - statusBarStateController.expansionChanges.collect { isExpanded -> + statusBarStateController.expansionChanges.collectLatest { isExpanded -> + // Give keyguard events time to propagate, in case this expansion is part of the + // keyguard transition and not the user expanding the shade + yield() if (isExpanded) { unseenNotifications.clear() } @@ -276,3 +313,6 @@ constructor( private val SEEN_TIMEOUT = 5.seconds } } + +private val TransitionStep.isScreenTurningOff: Boolean get() = + transitionState == TransitionState.STARTED && to != KeyguardState.GONE
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt index 8eef3f36433d..0ed41758f215 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt @@ -20,7 +20,6 @@ import android.service.notification.StatusBarNotification import com.android.systemui.ForegroundServiceNotificationListener import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.people.widget.PeopleSpaceWidgetManager import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption import com.android.systemui.statusbar.NotificationListener @@ -117,9 +116,7 @@ class NotificationsControllerImpl @Inject constructor( notificationLogger.setUpWithContainer(listContainer) peopleSpaceWidgetManager.attach(notificationListener) fgsNotifListener.init() - if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_MONITOR_ENABLED)) { - memoryMonitor.get().init() - } + memoryMonitor.get().init() } // TODO: Convert all functions below this line into listeners instead of public methods diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java index 112d48c115c2..00b9aa42ab26 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/AnimationProperties.java @@ -32,6 +32,7 @@ public class AnimationProperties { public long duration; public long delay; private ArrayMap<Property, Interpolator> mInterpolatorMap; + private Consumer<Property> mAnimationCancelAction; private Consumer<Property> mAnimationEndAction; /** @@ -50,27 +51,43 @@ public class AnimationProperties { * @return a listener that will be added for a given property during its animation. */ public AnimatorListenerAdapter getAnimationFinishListener(Property property) { - if (mAnimationEndAction == null) { + if (mAnimationEndAction == null && mAnimationCancelAction == null) { return null; } + Consumer<Property> cancelAction = mAnimationCancelAction; Consumer<Property> endAction = mAnimationEndAction; return new AnimatorListenerAdapter() { private boolean mCancelled; @Override public void onAnimationCancel(Animator animation) { - mCancelled = true; + mCancelled = true; + if (cancelAction != null) { + cancelAction.accept(property); + } } @Override public void onAnimationEnd(Animator animation) { - if (!mCancelled) { + if (!mCancelled && endAction != null) { endAction.accept(property); } } }; } + /** + * Add a callback for animation cancellation. + */ + public AnimationProperties setAnimationCancelAction(Consumer<Property> listener) { + mAnimationCancelAction = listener; + return this; + } + + /** + * Add a callback for animation ending successfully. The callback will not be called when the + * animations is cancelled. + */ public AnimationProperties setAnimationEndAction(Consumer<Property> listener) { mAnimationEndAction = listener; return this; 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 0195d4532ae0..3a1272fa32e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java @@ -326,6 +326,13 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn @Nullable ActivityLaunchAnimator.Controller animationController, UserHandle userHandle); + /** Starts an activity intent that dismisses keyguard. */ + void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned, + boolean dismissShade, boolean disallowEnterPictureInPictureWhileLaunching, + Callback callback, int flags, + @Nullable ActivityLaunchAnimator.Controller animationController, + UserHandle userHandle, @Nullable String customMessage); + void readyForKeyguardDone(); void executeRunnableDismissingKeyguard(Runnable runnable, @@ -339,7 +346,8 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn boolean dismissShade, boolean afterKeyguardGone, boolean deferred, - boolean willAnimateOnKeyguard); + boolean willAnimateOnKeyguard, + @Nullable String customMessage); void resetUserExpandedStates(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 7596ce08a53c..0960efb7388f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -2414,11 +2414,22 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } @Override + public void startActivityDismissingKeyguard(Intent intent, boolean onlyProvisioned, + boolean dismissShade, boolean disallowEnterPictureInPictureWhileLaunching, + Callback callback, int flags, + @androidx.annotation.Nullable ActivityLaunchAnimator.Controller animationController, + UserHandle userHandle) { + startActivityDismissingKeyguard(intent, onlyProvisioned, dismissShade, + disallowEnterPictureInPictureWhileLaunching, callback, flags, animationController, + userHandle, null /* customMessage */); + } + + @Override public void startActivityDismissingKeyguard(final Intent intent, boolean onlyProvisioned, final boolean dismissShade, final boolean disallowEnterPictureInPictureWhileLaunching, final Callback callback, int flags, @Nullable ActivityLaunchAnimator.Controller animationController, - final UserHandle userHandle) { + final UserHandle userHandle, @Nullable String customMessage) { if (onlyProvisioned && !mDeviceProvisionedController.isDeviceProvisioned()) return; final boolean willLaunchResolverActivity = @@ -2505,7 +2516,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { && mKeyguardStateController.isOccluded(); boolean deferred = !occluded; executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShadeDirectly, - willLaunchResolverActivity, deferred /* deferred */, animate); + willLaunchResolverActivity, deferred /* deferred */, animate, + customMessage /* customMessage */); } /** @@ -2558,7 +2570,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { final boolean afterKeyguardGone, final boolean deferred) { executeRunnableDismissingKeyguard(runnable, cancelAction, dismissShade, afterKeyguardGone, - deferred, false /* willAnimateOnKeyguard */); + deferred, false /* willAnimateOnKeyguard */, null /* customMessage */); } @Override @@ -2567,7 +2579,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { final boolean dismissShade, final boolean afterKeyguardGone, final boolean deferred, - final boolean willAnimateOnKeyguard) { + final boolean willAnimateOnKeyguard, + @Nullable String customMessage) { OnDismissAction onDismissAction = new OnDismissAction() { @Override public boolean onDismiss() { @@ -2596,7 +2609,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { return willAnimateOnKeyguard; } }; - dismissKeyguardThenExecute(onDismissAction, cancelAction, afterKeyguardGone); + dismissKeyguardThenExecute(onDismissAction, cancelAction, afterKeyguardGone, customMessage); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -2614,6 +2627,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } mRemoteInputManager.closeRemoteInputs(); if (mLockscreenUserManager.isCurrentProfile(getSendingUserId())) { + mShadeLogger.d("ACTION_CLOSE_SYSTEM_DIALOGS intent: closing shade"); int flags = CommandQueue.FLAG_EXCLUDE_NONE; if (reason != null) { if (reason.equals(SYSTEM_DIALOG_REASON_RECENT_APPS)) { @@ -2628,6 +2642,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } } mShadeController.animateCollapseShade(flags); + } else { + mShadeLogger.d("ACTION_CLOSE_SYSTEM_DIALOGS intent: non-matching user ID"); } } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { if (mNotificationShadeWindowController != null) { @@ -2674,6 +2690,12 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction, boolean afterKeyguardGone) { + dismissKeyguardThenExecute(action, cancelAction, afterKeyguardGone, null); + } + + @Override + public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction, + boolean afterKeyguardGone, String customMessage) { if (!action.willRunAnimationOnKeyguard() && mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP && mKeyguardStateController.canDismissLockScreen() @@ -2686,7 +2708,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } if (mKeyguardStateController.isShowing()) { mStatusBarKeyguardViewManager.dismissWithAction(action, cancelAction, - afterKeyguardGone); + afterKeyguardGone, customMessage); } else { // If the keyguard isn't showing but the device is dreaming, we should exit the dream. if (mKeyguardUpdateMonitor.isDreaming()) { @@ -2694,7 +2716,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { } action.onDismiss(); } + } + /** * Notify the shade controller that the current user changed * @@ -2899,6 +2923,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override public void postStartActivityDismissingKeyguard(Intent intent, int delay, @Nullable ActivityLaunchAnimator.Controller animationController) { + postStartActivityDismissingKeyguard(intent, delay, animationController, + null /* customMessage */); + } + + @Override + public void postStartActivityDismissingKeyguard(Intent intent, int delay, + @Nullable ActivityLaunchAnimator.Controller animationController, + @Nullable String customMessage) { mMainExecutor.executeDelayed( () -> startActivityDismissingKeyguard(intent, true /* onlyProvisioned */, @@ -2907,7 +2939,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { null /* callback */, 0 /* flags */, animationController, - getActivityUserHandle(intent)), + getActivityUserHandle(intent), customMessage), delay); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt index 8ee2c6f2c399..74ab47ff27a1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt @@ -29,6 +29,7 @@ import com.android.systemui.CoreStartable import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.util.Assert import com.android.systemui.util.sensors.AsyncSensorManager @@ -46,6 +47,7 @@ class KeyguardLiftController @Inject constructor( private val statusBarStateController: StatusBarStateController, private val asyncSensorManager: AsyncSensorManager, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, + private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor, private val dumpManager: DumpManager ) : Dumpable, CoreStartable { @@ -72,6 +74,7 @@ class KeyguardLiftController @Inject constructor( // Not listening anymore since trigger events unregister themselves isListening = false updateListeningState() + keyguardFaceAuthInteractor.onDeviceLifted() keyguardUpdateMonitor.requestFaceAuth( FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 3268032becf8..2814e8d8f97b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -49,6 +49,7 @@ import com.android.systemui.plugins.log.LogLevel; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.disableflags.DisableStateTracker; @@ -119,6 +120,9 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private final Object mLock = new Object(); private final KeyguardLogger mLogger; + // TODO(b/273443374): remove + private NotificationMediaManager mNotificationMediaManager; + private final ConfigurationController.ConfigurationListener mConfigurationListener = new ConfigurationController.ConfigurationListener() { @Override @@ -283,7 +287,8 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat SecureSettings secureSettings, CommandQueue commandQueue, @Main Executor mainExecutor, - KeyguardLogger logger + KeyguardLogger logger, + NotificationMediaManager notificationMediaManager ) { super(view); mCarrierTextController = carrierTextController; @@ -335,6 +340,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat /* mask2= */ DISABLE2_SYSTEM_ICONS, this::updateViewState ); + mNotificationMediaManager = notificationMediaManager; } @Override @@ -484,8 +490,11 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat * (1.0f - mKeyguardHeadsUpShowingAmount); } - if (mSystemEventAnimator.isAnimationRunning()) { + if (mSystemEventAnimator.isAnimationRunning() + && !mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) { newAlpha = Math.min(newAlpha, mSystemEventAnimatorAlpha); + } else { + mView.setTranslationX(0); } boolean hideForBypass = @@ -625,11 +634,21 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat private StatusBarSystemEventDefaultAnimator getSystemEventAnimator(boolean isAnimationRunning) { return new StatusBarSystemEventDefaultAnimator(getResources(), (alpha) -> { - mSystemEventAnimatorAlpha = alpha; + // TODO(b/273443374): remove if-else condition + if (!mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) { + mSystemEventAnimatorAlpha = alpha; + } else { + mSystemEventAnimatorAlpha = 1f; + } updateViewState(); return Unit.INSTANCE; }, (translationX) -> { - mView.setTranslationX(translationX); + // TODO(b/273443374): remove if-else condition + if (!mNotificationMediaManager.isLockscreenWallpaperOnNotificationShade()) { + mView.setTranslationX(translationX); + } else { + mView.setTranslationX(0); + } return Unit.INSTANCE; }, isAnimationRunning); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt index 398985402e76..f7426451fa50 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt @@ -26,10 +26,10 @@ import android.view.WindowInsetsController.Appearance import com.android.internal.statusbar.LetterboxDetails import com.android.internal.util.ContrastColorUtil import com.android.internal.view.AppearanceRegion +import com.android.systemui.Dumpable +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent import java.io.PrintWriter import java.util.Arrays @@ -50,24 +50,20 @@ class LetterboxAppearance( * Responsible for calculating the [Appearance] and [AppearanceRegion] for the status bar when apps * are letterboxed. */ -@CentralSurfacesScope +@SysUISingleton class LetterboxAppearanceCalculator @Inject constructor( private val lightBarController: LightBarController, - private val dumpManager: DumpManager, + dumpManager: DumpManager, private val letterboxBackgroundProvider: LetterboxBackgroundProvider, -) : OnStatusBarViewInitializedListener, CentralSurfacesComponent.Startable { +) : OnStatusBarViewInitializedListener, Dumpable { - private var statusBarBoundsProvider: StatusBarBoundsProvider? = null - - override fun start() { - dumpManager.registerCriticalDumpable(javaClass.simpleName) { pw, _ -> dump(pw) } + init { + dumpManager.registerCriticalDumpable(this) } - override fun stop() { - dumpManager.unregisterDumpable(javaClass.simpleName) - } + private var statusBarBoundsProvider: StatusBarBoundsProvider? = null private var lastAppearance: Int? = null private var lastAppearanceRegions: Array<AppearanceRegion>? = null @@ -216,8 +212,8 @@ constructor( return this.intersect(other) } - private fun dump(printWriter: PrintWriter) { - printWriter.println( + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println( """ lastAppearance: ${lastAppearance?.toAppearanceString()} lastAppearanceRegion: ${Arrays.toString(lastAppearanceRegions)}, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt index 276375004f76..34c7059ec991 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt @@ -22,28 +22,25 @@ import android.graphics.Color import android.os.Handler import android.os.RemoteException import android.view.IWindowManager +import com.android.systemui.CoreStartable import com.android.systemui.Dumpable +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.dump.DumpManager -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent -import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope import java.io.PrintWriter import java.util.concurrent.Executor import javax.inject.Inject /** Responsible for providing information about the background of letterboxed apps. */ -@CentralSurfacesScope +@SysUISingleton class LetterboxBackgroundProvider @Inject constructor( private val windowManager: IWindowManager, @Background private val backgroundExecutor: Executor, - private val dumpManager: DumpManager, private val wallpaperManager: WallpaperManager, @Main private val mainHandler: Handler, -) : CentralSurfacesComponent.Startable, Dumpable { - +) : CoreStartable, Dumpable { @ColorInt var letterboxBackgroundColor: Int = Color.BLACK private set @@ -57,7 +54,6 @@ constructor( } override fun start() { - dumpManager.registerDumpable(javaClass.simpleName, this) fetchBackgroundColorInfo() wallpaperManager.addOnColorsChangedListener(wallpaperColorsListener, mainHandler) } @@ -74,11 +70,6 @@ constructor( } } - override fun stop() { - dumpManager.unregisterDumpable(javaClass.simpleName) - wallpaperManager.removeOnColorsChangedListener(wallpaperColorsListener) - } - override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println( """ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt new file mode 100644 index 000000000000..2e3f0d0abc0a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxModule.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.statusbar.phone + +import com.android.systemui.CoreStartable +import dagger.Binds +import dagger.Module +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +abstract class LetterboxModule { + @Binds + @IntoMap + @ClassKey(LetterboxBackgroundProvider::class) + abstract fun bindFeature(impl: LetterboxBackgroundProvider): CoreStartable +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java index 0814ea593a0b..c16877a999f3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java @@ -233,6 +233,11 @@ public class LockscreenWallpaper extends IWallpaperManagerCallback.Stub implemen }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + // TODO(b/273443374): remove + public boolean isLockscreenLiveWallpaperEnabled() { + return mWallpaperManager.isLockscreenLiveWallpaperEnabled(); + } + @Override public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { pw.println(getClass().getSimpleName() + ":"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 49b58df23fdb..70aab61ffed0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -656,9 +656,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } updateAlternateBouncerShowing(mAlternateBouncerInteractor.show()); + setKeyguardMessage(message, null); return; } + mViewMediatorCallback.setCustomMessage(message); if (afterKeyguardGone) { // we'll handle the dismiss action after keyguard is gone, so just show the // bouncer diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java index 2027305fdb99..bb223650facc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java @@ -191,7 +191,7 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh } @Override - protected void onStart() { + protected final void onStart() { super.onStart(); if (mDismissReceiver != null) { @@ -204,10 +204,18 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh mDialogManager.setShowing(this, true); mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, true) .commitUpdate(mContext.getDisplayId()); + + start(); } + /** + * Called when {@link #onStart} is called. Subclasses wishing to override {@link #onStart()} + * should override this method instead. + */ + protected void start() {} + @Override - protected void onStop() { + protected final void onStop() { super.onStop(); if (mDismissReceiver != null) { @@ -218,8 +226,16 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh mDialogManager.setShowing(this, false); mSysUiState.setFlag(QuickStepContract.SYSUI_STATE_DIALOG_SHOWING, false) .commitUpdate(mContext.getDisplayId()); + + stop(); } + /** + * Called when {@link #onStop} is called. Subclasses wishing to override {@link #onStop()} + * should override this method instead. + */ + protected void stop() {} + public void setShowForAllUsers(boolean show) { setShowForAllUsers(this, show); } 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..0cd3401ae541 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -175,15 +175,19 @@ 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, + PropertyAnimator.setProperty(keyguardView, AnimatableProperty.Y, currentY, AnimationProperties().setDuration(duration.toLong()), true /* animate */) - keyguardView.animate() + // Cancel any existing CUJs before starting the animation + interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) + + PropertyAnimator.setProperty( + keyguardView, AnimatableProperty.ALPHA, 1f, + AnimationProperties() + .setDelay(0) .setDuration(duration.toLong()) - .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) - .alpha(1f) - .withEndAction { + .setAnimationEndAction { aodUiAnimationPlaying = false // Lock the keyguard if it was waiting for the screen off animation to end. @@ -199,30 +203,23 @@ class UnlockedScreenOffAnimationController @Inject constructor( // Done going to sleep, reset this flag. decidedToAnimateGoingToSleep = null - // We need to unset the listener. These are persistent for future animators - keyguardView.animate().setListener(null) interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD) } - .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) - - interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) - } - - override fun onAnimationStart(animation: Animator?) { - interactionJankMonitor.begin( - mCentralSurfaces.notificationShadeWindowView, - CUJ_SCREEN_OFF_SHOW_AOD) - } - }) - .start() + .setAnimationCancelAction { + // 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 + interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) + } + .setCustomInterpolator(View.ALPHA, Interpolators.FAST_OUT_SLOW_IN), + true /* animate */) + interactionJankMonitor.begin( + mCentralSurfaces.notificationShadeWindowView, + CUJ_SCREEN_OFF_SHOW_AOD + ) } override fun onStartedWakingUp() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java index b0532d773f7b..f72e74b77aea 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java @@ -16,17 +16,15 @@ package com.android.systemui.statusbar.phone.dagger; -import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator; -import com.android.systemui.statusbar.phone.LetterboxBackgroundProvider; import com.android.systemui.statusbar.phone.SystemBarAttributesListener; -import java.util.Set; - import dagger.Binds; import dagger.Module; import dagger.multibindings.IntoSet; import dagger.multibindings.Multibinds; +import java.util.Set; + @Module interface CentralSurfacesStartableModule { @Multibinds @@ -34,16 +32,6 @@ interface CentralSurfacesStartableModule { @Binds @IntoSet - CentralSurfacesComponent.Startable letterboxAppearanceCalculator( - LetterboxAppearanceCalculator letterboxAppearanceCalculator); - - @Binds - @IntoSet CentralSurfacesComponent.Startable sysBarAttrsListener( SystemBarAttributesListener systemBarAttributesListener); - - @Binds - @IntoSet - CentralSurfacesComponent.Startable letterboxBgProvider( - LetterboxBackgroundProvider letterboxBackgroundProvider); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt index eaa145582ba3..b3d246164e87 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupR import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractorImpl import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter +import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxyImpl import com.android.systemui.statusbar.pipeline.mobile.util.SubscriptionManagerProxy @@ -53,6 +54,9 @@ import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap +import kotlinx.coroutines.flow.Flow +import java.util.function.Supplier +import javax.inject.Named @Module abstract class StatusBarPipelineModule { @@ -115,6 +119,17 @@ abstract class StatusBarPipelineModule { @Provides @SysUISingleton + @Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON) + fun provideFirstMobileSubShowingNetworkTypeIconProvider( + mobileIconsViewModel: MobileIconsViewModel, + ): Supplier<Flow<Boolean>> { + return Supplier<Flow<Boolean>> { + mobileIconsViewModel.firstMobileSubShowingNetworkTypeIcon + } + } + + @Provides + @SysUISingleton @WifiInputLog fun provideWifiInputLogBuffer(factory: LogBufferFactory): LogBuffer { return factory.create("WifiInputLog", 50) @@ -168,5 +183,8 @@ abstract class StatusBarPipelineModule { fun provideVerboseMobileViewLogBuffer(factory: LogBufferFactory): LogBuffer { return factory.create("VerboseMobileViewLog", 100) } + + const val FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON = + "FirstMobileSubShowingNetworkTypeIcon" } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index 9af5e836659f..40b8c90fb9f5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -32,6 +32,9 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -63,22 +66,36 @@ constructor( } .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) + private val firstMobileSubViewModel: StateFlow<MobileIconViewModelCommon?> = + subscriptionIdsFlow + .map { + if (it.isEmpty()) { + null + } else { + // Mobile icons get reversed by [StatusBarIconController], so the last element + // in this list will show up visually first. + commonViewModelForSub(it.last()) + } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), null) + + /** + * A flow that emits `true` if the mobile sub that's displayed first visually is showing its + * network type icon and `false` otherwise. + */ + val firstMobileSubShowingNetworkTypeIcon: StateFlow<Boolean> = + firstMobileSubViewModel + .flatMapLatest { firstMobileSubViewModel -> + firstMobileSubViewModel?.networkTypeIcon?.map { it != null } ?: flowOf(false) + } + .stateIn(scope, SharingStarted.WhileSubscribed(), false) + init { scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } } } fun viewModelForSub(subId: Int, location: StatusBarLocation): LocationBasedMobileViewModel { - val common = - mobileIconSubIdCache[subId] - ?: MobileIconViewModel( - subId, - interactor.createMobileConnectionInteractorForSubId(subId), - airplaneModeInteractor, - constants, - scope, - ) - .also { mobileIconSubIdCache[subId] = it } - + val common = commonViewModelForSub(subId) return LocationBasedMobileViewModel.viewModelForLocation( common, statusBarPipelineFlags, @@ -87,6 +104,18 @@ constructor( ) } + private fun commonViewModelForSub(subId: Int): MobileIconViewModelCommon { + return mobileIconSubIdCache[subId] + ?: MobileIconViewModel( + subId, + interactor.createMobileConnectionInteractorForSubId(subId), + airplaneModeInteractor, + constants, + scope, + ) + .also { mobileIconSubIdCache[subId] = it } + } + private fun removeInvalidModelsFromCache(subIds: List<Int>) { val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt index 9e8c814ca2a9..e819c4fc96ac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt @@ -36,7 +36,6 @@ import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWi import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.launch @@ -64,6 +63,7 @@ object WifiViewBinder { val activityOutView = view.requireViewById<ImageView>(R.id.wifi_out) val activityContainerView = view.requireViewById<View>(R.id.inout_container) val airplaneSpacer = view.requireViewById<View>(R.id.wifi_airplane_spacer) + val signalSpacer = view.requireViewById<View>(R.id.wifi_signal_spacer) view.isVisible = true iconView.isVisible = true @@ -133,6 +133,12 @@ object WifiViewBinder { } } + launch { + viewModel.isSignalSpacerVisible.distinctUntilChanged().collect { visible -> + signalSpacer.isVisible = visible + } + } + try { awaitCancellation() } finally { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt index c9a0786acc72..d9c214452ef1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt @@ -30,8 +30,8 @@ import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK -import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel +import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule.Companion.FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON import com.android.systemui.statusbar.pipeline.dagger.WifiTableLog import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel @@ -39,7 +39,9 @@ import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiIntera import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon +import java.util.function.Supplier import javax.inject.Inject +import javax.inject.Named import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted @@ -53,24 +55,24 @@ import kotlinx.coroutines.flow.stateIn /** * Models the UI state for the status bar wifi icon. * - * This class exposes three view models, one per status bar location: [home], [keyguard], and [qs]. - * In order to get the UI state for the wifi icon, you must use one of those view models (whichever - * is correct for your location). - * - * Internally, this class maintains the current state of the wifi icon and notifies those three view - * models of any changes. + * This is a singleton so that we don't have duplicate logs and should *not* be used directly to + * control views. Instead, use an instance of [LocationBasedWifiViewModel]. See + * [LocationBasedWifiViewModel.viewModelForLocation]. */ @SysUISingleton class WifiViewModel @Inject constructor( airplaneModeViewModel: AirplaneModeViewModel, + // TODO(b/238425913): The wifi icon shouldn't need to consume mobile information. A + // container-level view model should do the work instead. + @Named(FIRST_MOBILE_SUB_SHOWING_NETWORK_TYPE_ICON) + shouldShowSignalSpacerProvider: Supplier<Flow<Boolean>>, connectivityConstants: ConnectivityConstants, private val context: Context, @WifiTableLog wifiTableLogBuffer: TableLogBuffer, interactor: WifiInteractor, @Application private val scope: CoroutineScope, - statusBarPipelineFlags: StatusBarPipelineFlags, wifiConstants: WifiConstants, ) : WifiViewModelCommon { /** Returns the icon to use based on the given network. */ @@ -183,6 +185,8 @@ constructor( override val isAirplaneSpacerVisible: Flow<Boolean> = airplaneModeViewModel.isAirplaneModeIconVisible + override val isSignalSpacerVisible: Flow<Boolean> = shouldShowSignalSpacerProvider.get() + companion object { @StringRes @VisibleForTesting diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt index eccf02397a82..617e19200a0a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelCommon.kt @@ -39,4 +39,7 @@ interface WifiViewModelCommon { /** True if the airplane spacer view should be visible. */ val isAirplaneSpacerVisible: Flow<Boolean> + + /** True if the spacer between the wifi icon and the RAT icon should be visible. */ + val isSignalSpacerVisible: Flow<Boolean> } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java index e9f0dcb4eb51..928e0115287d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherController.java @@ -61,6 +61,7 @@ import javax.inject.Inject; * Manages the user switcher on the Keyguard. */ @KeyguardUserSwitcherScope +@Deprecated public class KeyguardUserSwitcherController extends ViewController<KeyguardUserSwitcherView> { private static final String TAG = "KeyguardUserSwitcherController"; diff --git a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java index 05e566690f57..29f16c7b924a 100644 --- a/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java +++ b/packages/SystemUI/src/com/android/systemui/toast/SystemUIToast.java @@ -272,10 +272,10 @@ public class SystemUIToast implements ToastPlugin.Toast { private static boolean showApplicationIcon(ApplicationInfo appInfo, PackageManager packageManager) { - if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP)) { + if (hasFlag(appInfo.flags, FLAG_UPDATED_SYSTEM_APP | FLAG_SYSTEM)) { return packageManager.getLaunchIntentForPackage(appInfo.packageName) != null; } - return !hasFlag(appInfo.flags, FLAG_SYSTEM); + return true; } private static boolean hasFlag(int flags, int flag) { diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java index e3ed2b405fb0..db7fa14b4cff 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java @@ -186,8 +186,7 @@ public class CsdWarningDialog extends SystemUIDialog } @Override - protected void onStart() { - super.onStart(); + protected void start() { mShowTime = System.currentTimeMillis(); synchronized (mTimerLock) { if (mNoUserActionRunnable != null) { @@ -198,7 +197,7 @@ public class CsdWarningDialog extends SystemUIDialog } @Override - protected void onStop() { + protected void stop() { synchronized (mTimerLock) { if (mCancelScheduledNoUserActionRunnable != null) { mCancelScheduledNoUserActionRunnable.run(); diff --git a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java index 5b188b24d7dd..d42b964d593b 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/SafetyWarningDialog.java @@ -95,8 +95,7 @@ abstract public class SafetyWarningDialog extends SystemUIDialog } @Override - protected void onStart() { - super.onStart(); + protected void start() { mShowTime = System.currentTimeMillis(); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 3c007f99a654..aa26e688c490 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -2343,6 +2343,13 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, } } + @VisibleForTesting + void clearInternalHandleAfterTest() { + if (mHandler != null) { + mHandler.removeCallbacksAndMessages(null); + } + } + private final class CustomDialog extends Dialog implements DialogInterface { public CustomDialog(Context context) { super(context, R.style.volume_dialog_theme); diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java index 87a167baf9bf..96936e3eb5f0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialog.java @@ -196,16 +196,14 @@ public class VolumePanelDialog extends SystemUIDialog implements LifecycleOwner } @Override - protected void onStart() { - super.onStart(); + protected void start() { Log.d(TAG, "onStart"); mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED); mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED); } @Override - protected void onStop() { - super.onStop(); + protected void stop() { Log.d(TAG, "onStop"); mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED); } diff --git a/packages/SystemUI/tests/robolectric/config/robolectric.properties b/packages/SystemUI/tests/robolectric/config/robolectric.properties index 2a75bd98bfe8..438d54c5a200 100644 --- a/packages/SystemUI/tests/robolectric/config/robolectric.properties +++ b/packages/SystemUI/tests/robolectric/config/robolectric.properties @@ -13,4 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -sdk=NEWEST_SDK
\ No newline at end of file +sdk=NEWEST_SDK +shadows=\ + com.android.systemui.testutils.shadow.ShadowLockPatternUtils \ + com.android.systemui.testutils.shadow.ShadowTestableLooper
\ No newline at end of file diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/testutils/shadow/ShadowLockPatternUtils.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/testutils/shadow/ShadowLockPatternUtils.java new file mode 100644 index 000000000000..b248ce356394 --- /dev/null +++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/testutils/shadow/ShadowLockPatternUtils.java @@ -0,0 +1,33 @@ +/* + * 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.testutils.shadow; + +import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE; + +import com.android.internal.widget.LockPatternUtils; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; + +@Implements(LockPatternUtils.class) +public class ShadowLockPatternUtils { + + @Implementation + protected int getCredentialTypeForUser(int userHandle) { + return CREDENTIAL_TYPE_NONE; + } +} diff --git a/packages/SystemUI/tests/robolectric/src/com/android/systemui/testutils/shadow/ShadowTestableLooper.java b/packages/SystemUI/tests/robolectric/src/com/android/systemui/testutils/shadow/ShadowTestableLooper.java new file mode 100644 index 000000000000..a9b8bc044554 --- /dev/null +++ b/packages/SystemUI/tests/robolectric/src/com/android/systemui/testutils/shadow/ShadowTestableLooper.java @@ -0,0 +1,37 @@ +/* + * 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.testutils.shadow; + +import static org.robolectric.Shadows.shadowOf; + +import android.testing.TestableLooper; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.RealObject; + +@Implements(TestableLooper.class) +public class ShadowTestableLooper { + @RealObject private TestableLooper mRealTestableLooper; + /** + * Process messages in the queue until no more are found. + */ + @Implementation + protected void processAllMessages() { + shadowOf(mRealTestableLooper.getLooper()).idle(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index b9f8dd945293..fc906dedfa1d 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -391,7 +391,7 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { listenerArgumentCaptor.getValue().onCurrentClockChanged(); mExecutor.runAllReady(); - assertEquals(View.GONE, mFakeDateView.getVisibility()); + assertEquals(View.INVISIBLE, mFakeDateView.getVisibility()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index d760189bcfd6..f4df26dec89e 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -16,8 +16,6 @@ package com.android.keyguard; -import static android.view.WindowInsets.Type.ime; - import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT; import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED; @@ -29,9 +27,9 @@ import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -67,6 +65,7 @@ import com.android.systemui.classifier.FalsingA11yDelegate; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; @@ -163,6 +162,10 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor<KeyguardSecurityContainer.SwipeListener> mSwipeListenerArgumentCaptor; + @Captor + private ArgumentCaptor<KeyguardSecurityViewFlipperController.OnViewInflatedCallback> + mOnViewInflatedCallbackArgumentCaptor; + private KeyguardSecurityContainerController mKeyguardSecurityContainerController; private KeyguardPasswordViewController mKeyguardPasswordViewController; private KeyguardPasswordView mKeyguardPasswordView; @@ -183,8 +186,6 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class))) .thenReturn(mAdminSecondaryLockScreenController); when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController); - when(mKeyguardSecurityViewFlipperController.getSecurityView(any(SecurityMode.class), - any(KeyguardSecurityCallback.class))).thenReturn(mInputViewController); mKeyguardPasswordView = spy((KeyguardPasswordView) LayoutInflater.from(mContext).inflate( R.layout.keyguard_password_view, null)); when(mKeyguardPasswordView.getRootView()).thenReturn(mSecurityViewFlipper); @@ -208,7 +209,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mConfigurationController, mFalsingCollector, mFalsingManager, mUserSwitcherController, mFeatureFlags, mGlobalSettings, mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate, - mTelephonyManager, mViewMediatorCallback, mAudioManager); + mTelephonyManager, mViewMediatorCallback, mAudioManager, + mock(KeyguardFaceAuthInteractor.class)); } @Test @@ -228,29 +230,19 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { mKeyguardSecurityContainerController.showSecurityScreen(mode); if (mode == SecurityMode.Invalid) { verify(mKeyguardSecurityViewFlipperController, never()).getSecurityView( - any(SecurityMode.class), any(KeyguardSecurityCallback.class)); + any(SecurityMode.class), any(KeyguardSecurityCallback.class), any( + KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class) + ); } else { verify(mKeyguardSecurityViewFlipperController).getSecurityView( - eq(mode), any(KeyguardSecurityCallback.class)); + eq(mode), any(KeyguardSecurityCallback.class), any( + KeyguardSecurityViewFlipperController.OnViewInflatedCallback.class) + ); } } } @Test - public void startDisappearAnimation_animatesKeyboard() { - when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( - SecurityMode.Password); - when(mKeyguardSecurityViewFlipperController.getSecurityView( - eq(SecurityMode.Password), any(KeyguardSecurityCallback.class))) - .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); - mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password); - - mKeyguardSecurityContainerController.startDisappearAnimation(null); - verify(mWindowInsetsController).controlWindowInsetsAnimation( - eq(ime()), anyLong(), any(), any(), any()); - } - - @Test public void onResourcesUpdate_callsThroughOnRotationChange() { clearInvocations(mView); @@ -298,9 +290,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Test public void showSecurityScreen_oneHandedMode_flagDisabled_noOneHandedMode() { mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, false); - when(mKeyguardSecurityViewFlipperController.getSecurityView( - eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class))) - .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); + setupGetSecurityView(SecurityMode.Pattern); mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), @@ -312,11 +302,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Test public void showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode() { mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true); - when(mKeyguardSecurityViewFlipperController.getSecurityView( - eq(SecurityMode.Pattern), any(KeyguardSecurityCallback.class))) - .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController); - - mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern); + setupGetSecurityView(SecurityMode.Pattern); verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager), eq(mUserSwitcherController), any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), @@ -326,9 +312,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Test public void showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() { mTestableResources.addOverride(R.bool.can_use_one_handed_bouncer, true); - setupGetSecurityView(); + setupGetSecurityView(SecurityMode.Password); - mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password); verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager), eq(mUserSwitcherController), any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class), @@ -340,17 +325,18 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { ArgumentCaptor<KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback> captor = ArgumentCaptor.forClass( KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class); + setupGetSecurityView(SecurityMode.Password); - setupGetSecurityView(); - - mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password); verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class), any(UserSwitcherController.class), captor.capture(), eq(mFalsingA11yDelegate)); captor.getValue().showUnlockToContinueMessage(); + getViewControllerImmediately(); verify(mKeyguardPasswordViewControllerMock).showMessage( - getContext().getString(R.string.keyguard_unlock_to_continue), null); + /* message= */ getContext().getString(R.string.keyguard_unlock_to_continue), + /* colorState= */ null, + /* animated= */ true); } @Test @@ -453,7 +439,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { KeyguardSecurityContainer.SwipeListener registeredSwipeListener = getRegisteredSwipeListener(); when(mKeyguardUpdateMonitor.isFaceDetectionRunning()).thenReturn(false); - setupGetSecurityView(); + setupGetSecurityView(SecurityMode.Password); registeredSwipeListener.onSwipeUp(); @@ -479,11 +465,14 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { getRegisteredSwipeListener(); when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)) .thenReturn(true); - setupGetSecurityView(); + setupGetSecurityView(SecurityMode.Password); + clearInvocations(mKeyguardSecurityViewFlipperController); registeredSwipeListener.onSwipeUp(); + getViewControllerImmediately(); - verify(mKeyguardPasswordViewControllerMock).showMessage(null, null); + verify(mKeyguardPasswordViewControllerMock).showMessage(/* message= */ + null, /* colorState= */ null, /* animated= */ true); } @Test @@ -492,11 +481,12 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { getRegisteredSwipeListener(); when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)) .thenReturn(false); - setupGetSecurityView(); + setupGetSecurityView(SecurityMode.Password); registeredSwipeListener.onSwipeUp(); - verify(mKeyguardPasswordViewControllerMock, never()).showMessage(null, null); + verify(mKeyguardPasswordViewControllerMock, never()).showMessage(/* message= */ + null, /* colorState= */ null, /* animated= */ true); } @Test @@ -510,10 +500,15 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { configurationListenerArgumentCaptor.getValue().onDensityOrFontScaleChanged(); - verify(mView).onDensityOrFontScaleChanged(); verify(mKeyguardSecurityViewFlipperController).clearViews(); - verify(mKeyguardSecurityViewFlipperController).getSecurityView(eq(SecurityMode.PIN), - any(KeyguardSecurityCallback.class)); + verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView( + eq(SecurityMode.PIN), + any(KeyguardSecurityCallback.class), + mOnViewInflatedCallbackArgumentCaptor.capture()); + + mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController); + + verify(mView).onDensityOrFontScaleChanged(); } @Test @@ -527,12 +522,17 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { configurationListenerArgumentCaptor.getValue().onThemeChanged(); - verify(mView).reloadColors(); verify(mKeyguardSecurityViewFlipperController).clearViews(); - verify(mKeyguardSecurityViewFlipperController).getSecurityView(eq(SecurityMode.PIN), - any(KeyguardSecurityCallback.class)); + verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView( + eq(SecurityMode.PIN), + any(KeyguardSecurityCallback.class), + mOnViewInflatedCallbackArgumentCaptor.capture()); + + mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController); + verify(mView).reset(); verify(mKeyguardSecurityViewFlipperController).reset(); + verify(mView).reloadColors(); } @Test @@ -546,10 +546,15 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { configurationListenerArgumentCaptor.getValue().onUiModeChanged(); - verify(mView).reloadColors(); verify(mKeyguardSecurityViewFlipperController).clearViews(); - verify(mKeyguardSecurityViewFlipperController).getSecurityView(eq(SecurityMode.PIN), - any(KeyguardSecurityCallback.class)); + verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView( + eq(SecurityMode.PIN), + any(KeyguardSecurityCallback.class), + mOnViewInflatedCallbackArgumentCaptor.capture()); + + mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController); + + verify(mView).reloadColors(); } @Test @@ -612,6 +617,11 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Test public void testOnStartingToHide() { mKeyguardSecurityContainerController.onStartingToHide(); + verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class), + any(KeyguardSecurityCallback.class), + mOnViewInflatedCallbackArgumentCaptor.capture()); + + mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated(mInputViewController); verify(mInputViewController).onStartingToHide(); } @@ -671,26 +681,17 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { verify(mView).updatePositionByTouchX(1.0f); } - - @Test - public void testReinflateViewFlipper() { - mKeyguardSecurityContainerController.reinflateViewFlipper(() -> {}); - verify(mKeyguardSecurityViewFlipperController).clearViews(); - verify(mKeyguardSecurityViewFlipperController).getSecurityView(any(SecurityMode.class), - any(KeyguardSecurityCallback.class)); - } - @Test public void testReinflateViewFlipper_asyncBouncerFlagOn() { when(mFeatureFlags.isEnabled(Flags.ASYNC_INFLATE_BOUNCER)).thenReturn(true); - KeyguardSecurityViewFlipperController.OnViewInflatedListener onViewInflatedListener = - () -> { + KeyguardSecurityViewFlipperController.OnViewInflatedCallback onViewInflatedCallback = + controller -> { }; - mKeyguardSecurityContainerController.reinflateViewFlipper(onViewInflatedListener); + mKeyguardSecurityContainerController.reinflateViewFlipper(onViewInflatedCallback); verify(mKeyguardSecurityViewFlipperController).clearViews(); verify(mKeyguardSecurityViewFlipperController).asynchronouslyInflateView( any(SecurityMode.class), - any(KeyguardSecurityCallback.class), eq(onViewInflatedListener)); + any(KeyguardSecurityCallback.class), eq(onViewInflatedCallback)); } @Test @@ -713,14 +714,17 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { return mSwipeListenerArgumentCaptor.getValue(); } - private void attachView() { - mKeyguardSecurityContainerController.onViewAttached(); - verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallback.capture()); + private void setupGetSecurityView(SecurityMode securityMode) { + mKeyguardSecurityContainerController.showSecurityScreen(securityMode); + getViewControllerImmediately(); } - private void setupGetSecurityView() { - when(mKeyguardSecurityViewFlipperController.getSecurityView( - any(), any(KeyguardSecurityCallback.class))) - .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewControllerMock); + private void getViewControllerImmediately() { + verify(mKeyguardSecurityViewFlipperController, atLeastOnce()).getSecurityView( + any(SecurityMode.class), any(), + mOnViewInflatedCallbackArgumentCaptor.capture()); + mOnViewInflatedCallbackArgumentCaptor.getValue().onViewInflated( + (KeyguardInputViewController) mKeyguardPasswordViewControllerMock); + } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java index eaf7b1ec2100..cd187540ba74 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityViewFlipperControllerTest.java @@ -78,8 +78,6 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { private KeyguardSecurityCallback mKeyguardSecurityCallback; @Mock private FeatureFlags mFeatureFlags; - @Mock - private ViewMediatorCallback mViewMediatorCallback; private KeyguardSecurityViewFlipperController mKeyguardSecurityViewFlipperController; @@ -96,7 +94,7 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { mKeyguardSecurityViewFlipperController = new KeyguardSecurityViewFlipperController(mView, mLayoutInflater, mAsyncLayoutInflater, mKeyguardSecurityViewControllerFactory, - mEmergencyButtonControllerFactory, mFeatureFlags, mViewMediatorCallback); + mEmergencyButtonControllerFactory, mFeatureFlags); } @Test @@ -108,17 +106,29 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { reset(mLayoutInflater); when(mLayoutInflater.inflate(anyInt(), eq(mView), eq(false))) .thenReturn(mInputView); - mKeyguardSecurityViewFlipperController.getSecurityView(mode, mKeyguardSecurityCallback); - if (mode == SecurityMode.Invalid || mode == SecurityMode.None) { - verify(mLayoutInflater, never()).inflate( - anyInt(), any(ViewGroup.class), anyBoolean()); - } else { - verify(mLayoutInflater).inflate(anyInt(), eq(mView), eq(false)); - } + mKeyguardSecurityViewFlipperController.getSecurityView(mode, mKeyguardSecurityCallback, + controller -> { + if (mode == SecurityMode.Invalid || mode == SecurityMode.None) { + verify(mLayoutInflater, never()).inflate( + anyInt(), any(ViewGroup.class), anyBoolean()); + } else { + verify(mLayoutInflater).inflate(anyInt(), eq(mView), eq(false)); + } + }); } } @Test + public void getSecurityView_NotInflated() { + mKeyguardSecurityViewFlipperController.clearViews(); + mKeyguardSecurityViewFlipperController.getSecurityView(SecurityMode.PIN, + mKeyguardSecurityCallback, + controller -> {}); + verify(mAsyncLayoutInflater).inflate(anyInt(), eq(mView), any( + AsyncLayoutInflater.OnInflateFinishedListener.class)); + } + + @Test public void asynchronouslyInflateView() { mKeyguardSecurityViewFlipperController.asynchronouslyInflateView(SecurityMode.PIN, mKeyguardSecurityCallback, null); @@ -136,7 +146,6 @@ public class KeyguardSecurityViewFlipperControllerTest extends SysuiTestCase { argumentCaptor.getValue().onInflateFinished( LayoutInflater.from(getContext()).inflate(R.layout.keyguard_password_view, null), R.layout.keyguard_password_view, mView); - verify(mViewMediatorCallback).setNeedsInput(anyBoolean()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt index 58d906907488..dd87f6e7f64b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt @@ -18,10 +18,10 @@ package com.android.systemui.biometrics import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -36,7 +36,8 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) + +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt index bce98cf116d4..9f789e4cbd18 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.biometrics import android.hardware.biometrics.BiometricAuthenticator import android.os.Bundle -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.view.View @@ -37,7 +37,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest class AuthBiometricFingerprintViewTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index b4696e49e9aa..6d4c467aca7d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -25,7 +25,6 @@ import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.os.Handler import android.os.IBinder import android.os.UserManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.testing.ViewUtils @@ -34,6 +33,7 @@ import android.view.View import android.view.WindowInsets import android.view.WindowManager import android.widget.ScrollView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.internal.widget.LockPatternUtils @@ -62,7 +62,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest class AuthContainerViewTest : SysuiTestCase() { @@ -170,24 +170,16 @@ class AuthContainerViewTest : SysuiTestCase() { } @Test - fun testFocusLossAfterRotating() { + fun testActionCancel_panelInteractionDetectorDisable() { val container = initializeFingerprintContainer() - waitForIdleSync() - - val requestID = authContainer?.requestId ?: 0L - - verify(callback).onDialogAnimatedIn(requestID) - container.onOrientationChanged() - container.onWindowFocusChanged(false) - waitForIdleSync() - - verify(callback, never()).onDismissed( - eq(AuthDialogCallback.DISMISSED_USER_CANCELED), - eq<ByteArray?>(null), /* credentialAttestation */ - eq(requestID) + container.mBiometricCallback.onAction( + AuthBiometricView.Callback.ACTION_USER_CANCELED ) + waitForIdleSync() + verify(panelInteractionDetector).disable() } + @Test fun testActionAuthenticated_sendsDismissedAuthenticated() { val container = initializeFingerprintContainer() @@ -489,7 +481,7 @@ class AuthContainerViewTest : SysuiTestCase() { private fun AuthContainerView.addToView() { ViewUtils.attachView(this) waitForIdleSync() - assertThat(isAttachedToWindow).isTrue() + assertThat(isAttachedToWindow()).isTrue() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index c068efb1b5d4..0f20ace49a47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -75,7 +75,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.UserManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; @@ -83,6 +82,7 @@ import android.view.DisplayInfo; import android.view.Surface; import android.view.WindowManager; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; @@ -117,7 +117,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class AuthControllerTest extends SysuiTestCase { @@ -265,7 +265,7 @@ public class AuthControllerTest extends SysuiTestCase { mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(faceProps); // Ensures that the operations posted on the handler get executed. - mTestableLooper.processAllMessages(); + waitForIdleSync(); } // Callback tests @@ -285,14 +285,14 @@ public class AuthControllerTest extends SysuiTestCase { mFpAuthenticatorsRegisteredCaptor.capture()); verify(mFaceManager).addAuthenticatorsRegisteredCallback( mFaceAuthenticatorsRegisteredCaptor.capture()); - mTestableLooper.processAllMessages(); + waitForIdleSync(); verify(mFingerprintManager, never()).registerBiometricStateListener(any()); verify(mFaceManager, never()).registerBiometricStateListener(any()); mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of()); mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of()); - mTestableLooper.processAllMessages(); + waitForIdleSync(); verify(mFingerprintManager).registerBiometricStateListener(any()); verify(mFaceManager).registerBiometricStateListener(any()); @@ -316,7 +316,7 @@ public class AuthControllerTest extends SysuiTestCase { // Emulates a device with no authenticators (empty list). mFpAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of()); mFaceAuthenticatorsRegisteredCaptor.getValue().onAllAuthenticatorsRegistered(List.of()); - mTestableLooper.processAllMessages(); + waitForIdleSync(); verify(mFingerprintManager).registerBiometricStateListener( mBiometricStateCaptor.capture()); @@ -328,7 +328,7 @@ public class AuthControllerTest extends SysuiTestCase { listener.onEnrollmentsChanged(0 /* userId */, 0xbeef /* sensorId */, true /* hasEnrollments */); } - mTestableLooper.processAllMessages(); + waitForIdleSync(); // Nothing should crash. } @@ -692,7 +692,7 @@ public class AuthControllerTest extends SysuiTestCase { switchTask("other_package"); showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); - mTestableLooper.processAllMessages(); + waitForIdleSync(); assertNull(mAuthController.mCurrentDialog); assertNull(mAuthController.mReceiver); @@ -709,7 +709,7 @@ public class AuthControllerTest extends SysuiTestCase { switchTask("other_package"); mAuthController.mTaskStackListener.onTaskStackChanged(); - mTestableLooper.processAllMessages(); + waitForIdleSync(); assertNull(mAuthController.mCurrentDialog); assertNull(mAuthController.mReceiver); @@ -742,7 +742,7 @@ public class AuthControllerTest extends SysuiTestCase { showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); mAuthController.mBroadcastReceiver.onReceive(mContext, intent); - mTestableLooper.processAllMessages(); + waitForIdleSync(); assertNull(mAuthController.mCurrentDialog); assertNull(mAuthController.mReceiver); @@ -1021,4 +1021,9 @@ public class AuthControllerTest extends SysuiTestCase { return dialog; } } + + @Override + protected void waitForIdleSync() { + mTestableLooper.processAllMessages(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt index b41053cdea50..ef750be90b4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt @@ -51,32 +51,37 @@ class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() { @Test fun testEnableDetector_expandWithTrack_shouldPostRunnable() { detector.enable(action) - // simulate notification expand - shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f) + shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f) verify(action).run() } @Test fun testEnableDetector_trackOnly_shouldPostRunnable() { detector.enable(action) - // simulate notification expand - shadeExpansionStateManager.onPanelExpansionChanged(5566f, false, true, 5566f) + shadeExpansionStateManager.onPanelExpansionChanged(1.0f, false, true, 0f) verify(action).run() } @Test fun testEnableDetector_expandOnly_shouldPostRunnable() { detector.enable(action) - // simulate notification expand - shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, false, 5566f) + shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, false, 0f) verify(action).run() } @Test + fun testEnableDetector_expandWithoutFraction_shouldPostRunnable() { + detector.enable(action) + // simulate headsup notification + shadeExpansionStateManager.onPanelExpansionChanged(0.0f, true, false, 0f) + verifyZeroInteractions(action) + } + + @Test fun testEnableDetector_shouldNotPostRunnable() { detector.enable(action) detector.disable() - shadeExpansionStateManager.onPanelExpansionChanged(5566f, true, true, 5566f) + shadeExpansionStateManager.onPanelExpansionChanged(1.0f, true, true, 0f) verifyZeroInteractions(action) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java index 69c7f364d235..24a13a57dae7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java @@ -32,17 +32,20 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.display.DisplayManager; import android.os.Handler; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.Display; import android.view.Surface; import android.view.Surface.Rotation; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -51,11 +54,8 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import kotlin.Unit; -import kotlin.jvm.functions.Function0; - @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) public class BiometricDisplayListenerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt index c9ccdb36da89..88b6c39531e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.biometrics -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.logging.BiometricMessageDeferralLogger import com.android.systemui.SysuiTestCase @@ -33,7 +33,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class FaceHelpMessageDeferralTest : SysuiTestCase() { val threshold = .75f @Mock lateinit var logger: BiometricMessageDeferralLogger diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index 33345b5b9f75..c554af630106 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -34,7 +34,6 @@ import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.hardware.fingerprint.ISidefpsController import android.os.Handler -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.Display import android.view.DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS @@ -49,6 +48,7 @@ import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG import android.view.WindowMetrics +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.airbnb.lottie.LottieAnimationView import com.android.systemui.R @@ -90,7 +90,7 @@ private const val DISPLAY_ID = 2 private const val SENSOR_ID = 1 @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class SideFpsControllerTest : SysuiTestCase() { 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 b2c2ae7458ae..1faad8084535 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -25,7 +25,7 @@ import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROL import android.hardware.biometrics.BiometricOverlayConstants.ShowReason import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.IUdfpsOverlayControllerCallback -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater import android.view.MotionEvent @@ -80,7 +80,7 @@ private const val SENSOR_WIDTH = 30 private const val SENSOR_HEIGHT = 60 @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) class UdfpsControllerOverlayTest : SysuiTestCase() { 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 64c028e6a095..8d8b19050e4b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -58,7 +58,6 @@ import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.os.VibrationAttributes; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.LayoutInflater; import android.view.MotionEvent; @@ -68,6 +67,7 @@ import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceIdSequence; @@ -87,6 +87,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.ScreenLifecycle; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; @@ -124,7 +125,7 @@ import java.util.Optional; import javax.inject.Provider; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) public class UdfpsControllerTest extends SysuiTestCase { @@ -311,7 +312,8 @@ public class UdfpsControllerTest extends SysuiTestCase { mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker, mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor, mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker, - mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils); + mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils, + mock(KeyguardFaceAuthInteractor.class)); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java index 78fb5b00a21e..cd9189bef7f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java @@ -23,8 +23,8 @@ import android.hardware.biometrics.SensorLocationInternal; import android.hardware.biometrics.SensorProperties; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -35,7 +35,7 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java index 1bc237d422d6..5239966f1923 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java @@ -25,9 +25,9 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback; import android.os.RemoteException; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -40,7 +40,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) public class UdfpsDisplayModeTest extends SysuiTestCase { private static final int DISPLAY_ID = 0; 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 6d9acb92a5f4..af3a06b74943 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java @@ -27,10 +27,10 @@ import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.MotionEvent; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.shade.ShadeExpansionListener; @@ -40,7 +40,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt index b848413423d1..fea9d2d5a6be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.biometrics import android.os.Handler -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel import com.android.systemui.classifier.FalsingCollector @@ -39,10 +39,9 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.SystemClock -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.TestCoroutineScope -import kotlinx.coroutines.yield +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Before @@ -54,22 +53,27 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper +@kotlinx.coroutines.ExperimentalCoroutinesApi class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControllerBaseTest() { lateinit var keyguardBouncerRepository: KeyguardBouncerRepository @Mock private lateinit var bouncerLogger: TableLogBuffer + private lateinit var testScope: TestScope + @Before override fun setUp() { + testScope = TestScope() + allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread MockitoAnnotations.initMocks(this) keyguardBouncerRepository = KeyguardBouncerRepositoryImpl( mock(com.android.keyguard.ViewMediatorCallback::class.java), FakeSystemClock(), - TestCoroutineScope(), + testScope.backgroundScope, bouncerLogger, ) super.setUp() @@ -107,7 +111,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle @Test fun shadeLocked_showAlternateBouncer_unpauseAuth() = - runBlocking(IMMEDIATE) { + testScope.runTest { // GIVEN view is attached + on the SHADE_LOCKED (udfps view not showing) mController.onViewAttached() captureStatusBarStateListeners() @@ -116,7 +120,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle // WHEN alternate bouncer is requested val job = mController.listenForAlternateBouncerVisibility(this) keyguardBouncerRepository.setAlternateVisible(true) - yield() + runCurrent() // THEN udfps view will animate in & pause auth is updated to NOT pause verify(mView).animateInUdfpsBouncer(any()) @@ -128,7 +132,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle /** After migration to MODERN_BOUNCER, replaces UdfpsKeyguardViewControllerTest version */ @Test fun shouldPauseAuthBouncerShowing() = - runBlocking(IMMEDIATE) { + testScope.runTest { // GIVEN view attached and we're on the keyguard mController.onViewAttached() captureStatusBarStateListeners() @@ -138,15 +142,11 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle val job = mController.listenForBouncerExpansion(this) keyguardBouncerRepository.setPrimaryShow(true) keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) - yield() + runCurrent() // THEN UDFPS shouldPauseAuth == true assertTrue(mController.shouldPauseAuth()) job.cancel() } - - companion object { - private val IMMEDIATE = Dispatchers.Main.immediate - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt index c2a129be66a4..8b374ae54127 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt @@ -17,9 +17,9 @@ package com.android.systemui.biometrics import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.MotionEvent +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.UdfpsController.UdfpsOverlayController @@ -39,7 +39,7 @@ import org.mockito.Mockito.`when` as whenEver import org.mockito.junit.MockitoJUnit @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class UdfpsShellTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt index 07b4a649a604..f0759670a21b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt @@ -19,7 +19,7 @@ package com.android.systemui.biometrics import android.graphics.PointF import android.graphics.RectF import android.hardware.biometrics.SensorLocationInternal -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import android.testing.TestableLooper import android.testing.ViewUtils import android.view.LayoutInflater @@ -49,7 +49,7 @@ private const val SENSOR_Y = 250 private const val SENSOR_RADIUS = 10 @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class UdfpsViewTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsPopupMenuTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsPopupMenuTest.kt index 86e2bd3e0890..df6fa11ff72c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsPopupMenuTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsPopupMenuTest.kt @@ -22,6 +22,7 @@ import android.graphics.drawable.ShapeDrawable import android.testing.AndroidTestingRunner import android.util.DisplayMetrics import android.view.View +import android.view.ViewGroup import android.widget.PopupWindow.OnDismissListener import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.filters.SmallTest @@ -29,13 +30,17 @@ import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.activity.EmptyTestActivity import com.android.systemui.util.mockito.whenever +import com.android.systemui.widget.FakeListAdapter +import com.android.systemui.widget.FakeListAdapter.FakeListAdapterItem import com.google.common.truth.Truth.assertThat +import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mockito.mock import org.mockito.Mockito.spy import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @@ -52,10 +57,16 @@ open class ControlsPopupMenuTest : SysuiTestCase() { @Rule @JvmField val activityScenarioRule = ActivityScenarioRule(EmptyTestActivity::class.java) - private val testDisplayMetrics: DisplayMetrics = DisplayMetrics() + private val testDisplayMetrics = DisplayMetrics() + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } @Test - fun testDismissListenerWorks() = testPopup { popupMenu -> + fun testDismissListenerWorks() = testPopup { activity, popupMenu -> + popupMenu.setAdapter(FakeListAdapter()) val listener = mock(OnDismissListener::class.java) popupMenu.setOnDismissListener(listener) popupMenu.show() @@ -66,7 +77,9 @@ open class ControlsPopupMenuTest : SysuiTestCase() { } @Test - fun testPopupDoesntExceedMaxWidth() = testPopup { popupMenu -> + fun testPopupDoesntExceedMaxWidth() = testPopup { activity, popupMenu -> + popupMenu.setAdapter(FakeListAdapter()) + popupMenu.width = ViewGroup.LayoutParams.MATCH_PARENT testDisplayMetrics.widthPixels = DISPLAY_WIDTH_WIDE popupMenu.show() @@ -75,7 +88,28 @@ open class ControlsPopupMenuTest : SysuiTestCase() { } @Test - fun testPopupMarginsWidthLessMax() = testPopup { popupMenu -> + fun testPopupMarginsWidthLessMax() = testPopup { activity, popupMenu -> + popupMenu.setAdapter(FakeListAdapter()) + popupMenu.width = ViewGroup.LayoutParams.MATCH_PARENT + testDisplayMetrics.widthPixels = DISPLAY_WIDTH_NARROW + + popupMenu.show() + + assertThat(popupMenu.width).isEqualTo(DISPLAY_WIDTH_NARROW - 2 * HORIZONTAL_MARGIN) + } + + @Test + fun testWrapContentDoesntExceedMax() = testPopup { activity, popupMenu -> + popupMenu.setAdapter( + FakeListAdapter( + listOf( + FakeListAdapterItem({ _, _, _ -> + View(activity).apply { minimumWidth = MAX_WIDTH + 1 } + }) + ) + ) + ) + popupMenu.width = ViewGroup.LayoutParams.WRAP_CONTENT testDisplayMetrics.widthPixels = DISPLAY_WIDTH_NARROW popupMenu.show() @@ -83,10 +117,13 @@ open class ControlsPopupMenuTest : SysuiTestCase() { assertThat(popupMenu.width).isEqualTo(DISPLAY_WIDTH_NARROW - 2 * HORIZONTAL_MARGIN) } - private fun testPopup(test: (popup: ControlsPopupMenu) -> Unit) { + private fun testPopup(test: (activity: Activity, popup: ControlsPopupMenu) -> Unit) { activityScenarioRule.scenario.onActivity { activity -> val testActivity = setupActivity(activity) - test(ControlsPopupMenu(testActivity).apply { anchorView = View(testActivity) }) + test( + testActivity, + ControlsPopupMenu(testActivity).apply { anchorView = View(testActivity) } + ) } } 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 index 2489e043c7db..fc75d47c01b7 100644 --- 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 @@ -44,16 +44,20 @@ 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.domain.interactor.KeyguardTransitionInteractor 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.KeyguardState import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus +import com.android.systemui.keyguard.shared.model.TransitionStep 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.log.table.TableLogBuffer import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.FakeKeyguardStateController import com.android.systemui.statusbar.phone.KeyguardBypassController @@ -61,8 +65,11 @@ import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.util.mockito.KotlinArgumentCaptor import com.android.systemui.util.mockito.captureMany import com.android.systemui.util.mockito.whenever +import com.android.systemui.util.time.FakeSystemClock 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 @@ -88,8 +95,6 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations -import java.io.PrintWriter -import java.io.StringWriter @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -117,6 +122,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private lateinit var faceLockoutResetCallback: ArgumentCaptor<FaceManager.LockoutResetCallback> private lateinit var testDispatcher: TestDispatcher + private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository private lateinit var testScope: TestScope private lateinit var fakeUserRepository: FakeUserRepository private lateinit var authStatus: FlowValue<AuthenticationStatus?> @@ -183,8 +189,14 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private fun createDeviceEntryFaceAuthRepositoryImpl( fmOverride: FaceManager? = faceManager, bypassControllerOverride: KeyguardBypassController? = bypassController - ) = - DeviceEntryFaceAuthRepositoryImpl( + ): DeviceEntryFaceAuthRepositoryImpl { + val systemClock = FakeSystemClock() + val faceAuthBuffer = TableLogBuffer(10, "face auth", systemClock) + val faceDetectBuffer = TableLogBuffer(10, "face detect", systemClock) + keyguardTransitionRepository = FakeKeyguardTransitionRepository() + val keyguardTransitionInteractor = + KeyguardTransitionInteractor(keyguardTransitionRepository) + return DeviceEntryFaceAuthRepositoryImpl( mContext, fmOverride, fakeUserRepository, @@ -200,8 +212,12 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { keyguardRepository, keyguardInteractor, alternateBouncerInteractor, + faceDetectBuffer, + faceAuthBuffer, + keyguardTransitionInteractor, dumpManager, ) + } @Test fun faceAuthRunsAndProvidesAuthStatusUpdates() = @@ -764,6 +780,50 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } } + @Test + fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromDozing() = + testScope.runTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.DOZING, to = KeyguardState.GONE) + ) + + runCurrent() + verify(faceManager).scheduleWatchdog() + } + + @Test + fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromAod() = + testScope.runTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE) + ) + + runCurrent() + verify(faceManager).scheduleWatchdog() + } + + @Test + fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromLockscreen() = + testScope.runTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE) + ) + + runCurrent() + verify(faceManager).scheduleWatchdog() + } + + @Test + fun schedulesFaceManagerWatchdogWhenKeyguardIsGoneFromBouncer() = + testScope.runTest { + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(from = KeyguardState.PRIMARY_BOUNCER, to = KeyguardState.GONE) + ) + + runCurrent() + verify(faceManager).scheduleWatchdog() + } + private suspend fun TestScope.testGatingCheckForFaceAuth(gatingCheckModifier: () -> Unit) { initCollectors() allPreconditionsToRunFaceAuthAreTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt new file mode 100644 index 000000000000..3d1d2f46a65e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -0,0 +1,292 @@ +/* + * 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.domain.interactor + +import android.os.Handler +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.FaceAuthUiEvent +import com.android.keyguard.KeyguardSecurityModel +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.dump.logcatLogBuffer +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.keyguard.data.BouncerView +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.log.FaceAuthenticationLogger +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.statusbar.phone.KeyguardBypassController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.time.FakeSystemClock +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestCoroutineScheduler +import kotlinx.coroutines.test.TestScope +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.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.MockitoAnnotations + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class KeyguardFaceAuthInteractorTest : SysuiTestCase() { + + private lateinit var underTest: SystemUIKeyguardFaceAuthInteractor + private lateinit var testScope: TestScope + private lateinit var bouncerRepository: FakeKeyguardBouncerRepository + private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository + private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor + private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository + + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + val scheduler = TestCoroutineScheduler() + val dispatcher = StandardTestDispatcher(scheduler) + testScope = TestScope(dispatcher) + val featureFlags = FakeFeatureFlags() + featureFlags.set(Flags.FACE_AUTH_REFACTOR, true) + bouncerRepository = FakeKeyguardBouncerRepository() + faceAuthRepository = FakeDeviceEntryFaceAuthRepository() + keyguardTransitionRepository = FakeKeyguardTransitionRepository() + keyguardTransitionInteractor = KeyguardTransitionInteractor(keyguardTransitionRepository) + + underTest = + SystemUIKeyguardFaceAuthInteractor( + testScope.backgroundScope, + dispatcher, + faceAuthRepository, + PrimaryBouncerInteractor( + bouncerRepository, + mock(BouncerView::class.java), + mock(Handler::class.java), + mock(KeyguardStateController::class.java), + mock(KeyguardSecurityModel::class.java), + mock(PrimaryBouncerCallbackInteractor::class.java), + mock(FalsingCollector::class.java), + mock(DismissCallbackRegistry::class.java), + context, + keyguardUpdateMonitor, + mock(KeyguardBypassController::class.java), + ), + AlternateBouncerInteractor( + mock(StatusBarStateController::class.java), + mock(KeyguardStateController::class.java), + bouncerRepository, + mock(BiometricSettingsRepository::class.java), + FakeDeviceEntryFingerprintAuthRepository(), + FakeSystemClock(), + ), + keyguardTransitionInteractor, + featureFlags, + FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")), + keyguardUpdateMonitor, + ) + } + + @Test + fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromOffState() = + testScope.runTest { + underTest.start() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.OFF, + KeyguardState.LOCKSCREEN, + transitionState = TransitionState.STARTED + ) + ) + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo( + Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true) + ) + } + + @Test + fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() = + testScope.runTest { + underTest.start() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.AOD, + KeyguardState.LOCKSCREEN, + transitionState = TransitionState.STARTED + ) + ) + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo( + Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true) + ) + } + + @Test + fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromDozingState() = + testScope.runTest { + underTest.start() + + keyguardTransitionRepository.sendTransitionStep( + TransitionStep( + KeyguardState.DOZING, + KeyguardState.LOCKSCREEN, + transitionState = TransitionState.STARTED + ) + ) + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo( + Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true) + ) + } + + @Test + fun faceAuthIsRequestedWhenPrimaryBouncerIsVisible() = + testScope.runTest { + underTest.start() + + bouncerRepository.setPrimaryShow(false) + runCurrent() + + bouncerRepository.setPrimaryShow(true) + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, true)) + } + + @Test + fun faceAuthIsRequestedWhenAlternateBouncerIsVisible() = + testScope.runTest { + underTest.start() + + bouncerRepository.setAlternateVisible(false) + runCurrent() + + bouncerRepository.setAlternateVisible(true) + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo( + Pair( + FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN, + false + ) + ) + } + + @Test + fun faceAuthIsRequestedWhenUdfpsSensorTouched() = + testScope.runTest { + underTest.start() + + underTest.onUdfpsSensorTouched() + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false)) + } + + @Test + fun faceAuthIsRequestedWhenOnAssistantTriggeredOnLockScreen() = + testScope.runTest { + underTest.start() + + underTest.onAssistantTriggeredOnLockScreen() + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo( + Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true) + ) + } + + @Test + fun faceAuthIsRequestedWhenDeviceLifted() = + testScope.runTest { + underTest.start() + + underTest.onDeviceLifted() + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo( + Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true) + ) + } + + @Test + fun faceAuthIsRequestedWhenQsExpansionStared() = + testScope.runTest { + underTest.start() + + underTest.onQsExpansionStared() + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true)) + } + + @Test + fun faceAuthIsRequestedWhenNotificationPanelClicked() = + testScope.runTest { + underTest.start() + + underTest.onNotificationPanelClicked() + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo( + Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true) + ) + } + + @Test + fun faceAuthIsRequestedWhenSwipeUpOnBouncer() = + testScope.runTest { + underTest.start() + + underTest.onSwipeUpOnBouncer() + + runCurrent() + assertThat(faceAuthRepository.runningAuthRequest.value) + .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt new file mode 100644 index 000000000000..86f3062bca35 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt @@ -0,0 +1,75 @@ +/* + * 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.media.controls.util + +import android.testing.AndroidTestingRunner +import android.util.Pair as APair +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class MediaDataUtilsTest : SysuiTestCase() { + + @Test + fun testScaleFactor_zeroInput_returnsZero() { + val input = APair(0, 0) + val target = APair(100, 100) + + val scale = MediaDataUtils.getScaleFactor(input, target) + assertThat(scale).isEqualTo(0f) + } + + @Test + fun testScaleFactor_tooWide_scaleDown() { + val input = APair(400, 200) + val target = APair(100, 100) + + val scale = MediaDataUtils.getScaleFactor(input, target) + assertThat(scale).isEqualTo(0.5f) + } + + @Test + fun testScaleFactor_tooTall_scaleDown() { + val input = APair(200, 400) + val target = APair(100, 100) + + val scale = MediaDataUtils.getScaleFactor(input, target) + assertThat(scale).isEqualTo(0.5f) + } + + @Test + fun testScaleFactor_lessWide_scaleUp() { + val input = APair(50, 100) + val target = APair(100, 100) + + val scale = MediaDataUtils.getScaleFactor(input, target) + assertThat(scale).isEqualTo(2f) + } + + @Test + fun testScaleFactor_lessTall_scaleUp() { + val input = APair(100, 50) + val target = APair(100, 100) + + val scale = MediaDataUtils.getScaleFactor(input, target) + assertThat(scale).isEqualTo(2f) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index 9a0bd9e4b0df..f206409a071e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -255,10 +255,10 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mLocalBluetoothLeBroadcast); mIsBroadcasting = true; - mMediaOutputBaseDialogImpl.onStart(); + mMediaOutputBaseDialogImpl.start(); verify(mLocalBluetoothLeBroadcast).registerServiceCallBack(any(), any()); - mMediaOutputBaseDialogImpl.onStop(); + mMediaOutputBaseDialogImpl.stop(); verify(mLocalBluetoothLeBroadcast).unregisterServiceCallBack(any()); } @@ -269,8 +269,8 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase { mLocalBluetoothLeBroadcast); mIsBroadcasting = false; - mMediaOutputBaseDialogImpl.onStart(); - mMediaOutputBaseDialogImpl.onStop(); + mMediaOutputBaseDialogImpl.start(); + mMediaOutputBaseDialogImpl.stop(); verify(mLocalBluetoothLeBroadcast, never()).registerServiceCallBack(any(), any()); verify(mLocalBluetoothLeBroadcast, never()).unregisterServiceCallBack(any()); 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 ba29ca57cefb..22a5b21f71ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -63,8 +63,10 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock +import org.mockito.Mockito.doNothing import org.mockito.Mockito.isNull import org.mockito.Mockito.never +import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations @@ -75,7 +77,9 @@ import org.mockito.MockitoAnnotations internal class NoteTaskControllerTest : SysuiTestCase() { @Mock private lateinit var context: Context + @Mock private lateinit var workProfileContext: Context @Mock private lateinit var packageManager: PackageManager + @Mock private lateinit var workProfilePackageManager: PackageManager @Mock private lateinit var resolver: NoteTaskInfoResolver @Mock private lateinit var bubbles: Bubbles @Mock private lateinit var keyguardManager: KeyguardManager @@ -107,6 +111,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { .thenReturn(listOf(NOTE_TASK_PACKAGE_NAME)) whenever(activityManager.getRunningTasks(anyInt())).thenReturn(emptyList()) whenever(userManager.isManagedProfile(workUserInfo.id)).thenReturn(true) + whenever(context.resources).thenReturn(getContext().resources) } private fun createNoteTaskController( @@ -337,14 +342,14 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - fun showNoteTask_intentResolverReturnsNull_shouldDoNothing() { + fun showNoteTask_intentResolverReturnsNull_shouldShowToast() { whenever(resolver.resolveInfo(any(), any())).thenReturn(null) + val noteTaskController = spy(createNoteTaskController()) + doNothing().whenever(noteTaskController).showNoDefaultNotesAppToast() - createNoteTaskController() - .showNoteTask( - entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, - ) + noteTaskController.showNoteTask(entryPoint = NoteTaskEntryPoint.TAIL_BUTTON) + verify(noteTaskController).showNoDefaultNotesAppToast() verifyZeroInteractions(context, bubbles, eventLogger) } @@ -373,17 +378,17 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @Test fun showNoteTask_keyboardShortcut_shouldStartActivity() { val expectedInfo = - NOTE_TASK_INFO.copy( - entryPoint = NoteTaskEntryPoint.KEYBOARD_SHORTCUT, - isKeyguardLocked = true, - ) + 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!!, - ) + .showNoteTask( + entryPoint = expectedInfo.entryPoint!!, + ) val intentCaptor = argumentCaptor<Intent>() val userCaptor = argumentCaptor<UserHandle>() @@ -393,9 +398,9 @@ internal class NoteTaskControllerTest : SysuiTestCase() { 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) + .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK) assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT) - .isEqualTo(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) @@ -407,7 +412,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { // region setNoteTaskShortcutEnabled @Test fun setNoteTaskShortcutEnabled_setTrue() { - createNoteTaskController().setNoteTaskShortcutEnabled(value = true) + createNoteTaskController().setNoteTaskShortcutEnabled(value = true, userTracker.userHandle) val argument = argumentCaptor<ComponentName>() verify(context.packageManager) @@ -422,7 +427,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @Test fun setNoteTaskShortcutEnabled_setFalse() { - createNoteTaskController().setNoteTaskShortcutEnabled(value = false) + createNoteTaskController().setNoteTaskShortcutEnabled(value = false, userTracker.userHandle) val argument = argumentCaptor<ComponentName>() verify(context.packageManager) @@ -434,6 +439,47 @@ internal class NoteTaskControllerTest : SysuiTestCase() { assertThat(argument.value.className) .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name) } + + @Test + fun setNoteTaskShortcutEnabled_workProfileUser_setTrue() { + whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any())) + .thenReturn(workProfileContext) + whenever(workProfileContext.packageManager).thenReturn(workProfilePackageManager) + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + + createNoteTaskController().setNoteTaskShortcutEnabled(value = true, workUserInfo.userHandle) + + val argument = argumentCaptor<ComponentName>() + verify(workProfilePackageManager) + .setComponentEnabledSetting( + argument.capture(), + eq(COMPONENT_ENABLED_STATE_ENABLED), + eq(PackageManager.DONT_KILL_APP), + ) + assertThat(argument.value.className) + .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name) + } + + @Test + fun setNoteTaskShortcutEnabled_workProfileUser_setFalse() { + whenever(context.createContextAsUser(eq(workUserInfo.userHandle), any())) + .thenReturn(workProfileContext) + whenever(workProfileContext.packageManager).thenReturn(workProfilePackageManager) + userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUserInfo)) + + createNoteTaskController() + .setNoteTaskShortcutEnabled(value = false, workUserInfo.userHandle) + + val argument = argumentCaptor<ComponentName>() + verify(workProfilePackageManager) + .setComponentEnabledSetting( + argument.capture(), + eq(COMPONENT_ENABLED_STATE_DISABLED), + eq(PackageManager.DONT_KILL_APP), + ) + assertThat(argument.value.className) + .isEqualTo(CreateNoteTaskShortcutActivity::class.java.name) + } // endregion // region keyguard policy 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 ec4daee72cf8..28ed9d22a41b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -20,9 +20,11 @@ import android.test.suitebuilder.annotation.SmallTest import android.view.KeyEvent import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase +import com.android.systemui.settings.FakeUserTracker import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.android.wm.shell.bubbles.Bubbles import java.util.Optional @@ -46,6 +48,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { @Mock lateinit var roleManager: RoleManager private val clock = FakeSystemClock() private val executor = FakeExecutor(clock) + private val userTracker = FakeUserTracker() @Before fun setUp() { @@ -63,6 +66,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { isEnabled = isEnabled, roleManager = roleManager, backgroundExecutor = executor, + userTracker = userTracker, ) } @@ -71,7 +75,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { fun initialize() { createNoteTaskInitializer().initialize() - verify(controller).setNoteTaskShortcutEnabled(true) + verify(controller).setNoteTaskShortcutEnabled(eq(true), eq(userTracker.userHandle)) verify(commandQueue).addCallback(any()) verify(roleManager).addOnRoleHoldersChangedListenerAsUser(any(), any(), any()) } @@ -80,7 +84,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { fun initialize_flagDisabled() { createNoteTaskInitializer(isEnabled = false).initialize() - verify(controller, never()).setNoteTaskShortcutEnabled(any()) + verify(controller, never()).setNoteTaskShortcutEnabled(any(), any()) verify(commandQueue, never()).addCallback(any()) verify(roleManager, never()).addOnRoleHoldersChangedListenerAsUser(any(), any(), any()) } @@ -89,7 +93,7 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { fun initialize_bubblesNotPresent() { createNoteTaskInitializer(bubbles = null).initialize() - verify(controller, never()).setNoteTaskShortcutEnabled(any()) + verify(controller, never()).setNoteTaskShortcutEnabled(any(), any()) verify(commandQueue, never()).addCallback(any()) verify(roleManager, never()).addOnRoleHoldersChangedListenerAsUser(any(), any(), any()) } @@ -98,24 +102,36 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { // region handleSystemKey @Test fun handleSystemKey_receiveValidSystemKey_shouldShowNoteTask() { - createNoteTaskInitializer().callbacks.handleSystemKey(KeyEvent(KeyEvent.ACTION_DOWN, - 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)) + 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(KeyEvent.ACTION_DOWN, - 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/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt new file mode 100644 index 000000000000..77b3e69f4384 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt @@ -0,0 +1,189 @@ +/* + * 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.provider.Settings +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.qs.pipeline.shared.TileSpec +import com.android.systemui.util.settings.FakeSettings +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidTestingRunner::class) +class AutoAddSettingsRepositoryTest : SysuiTestCase() { + private val secureSettings = FakeSettings() + + private val testDispatcher = StandardTestDispatcher() + private val testScope = TestScope(testDispatcher) + + private lateinit var underTest: AutoAddSettingRepository + + @Before + fun setUp() { + underTest = + AutoAddSettingRepository( + secureSettings, + testDispatcher, + ) + } + + @Test + fun nonExistentSetting_emptySet() = + testScope.runTest { + val specs by collectLastValue(underTest.autoAddedTiles(0)) + + assertThat(specs).isEmpty() + } + + @Test + fun settingsChange_correctValues() = + testScope.runTest { + val userId = 0 + val specs by collectLastValue(underTest.autoAddedTiles(userId)) + + val value = "a,custom(b/c)" + storeForUser(value, userId) + + assertThat(specs).isEqualTo(value.toSet()) + + val newValue = "a" + storeForUser(newValue, userId) + + assertThat(specs).isEqualTo(newValue.toSet()) + } + + @Test + fun tilesForCorrectUsers() = + testScope.runTest { + val tilesFromUser0 by collectLastValue(underTest.autoAddedTiles(0)) + val tilesFromUser1 by collectLastValue(underTest.autoAddedTiles(1)) + + val user0Tiles = "a" + val user1Tiles = "custom(b/c)" + storeForUser(user0Tiles, 0) + storeForUser(user1Tiles, 1) + + assertThat(tilesFromUser0).isEqualTo(user0Tiles.toSet()) + assertThat(tilesFromUser1).isEqualTo(user1Tiles.toSet()) + } + + @Test + fun noInvalidTileSpecs() = + testScope.runTest { + val userId = 0 + val tiles by collectLastValue(underTest.autoAddedTiles(userId)) + + val specs = "d,custom(bad)" + storeForUser(specs, userId) + + assertThat(tiles).isEqualTo("d".toSet()) + } + + @Test + fun markAdded() = + testScope.runTest { + val userId = 0 + val specs = mutableSetOf(TileSpec.create("a")) + underTest.markTileAdded(userId, TileSpec.create("a")) + + assertThat(loadForUser(userId).toSet()).containsExactlyElementsIn(specs) + + specs.add(TileSpec.create("b")) + underTest.markTileAdded(userId, TileSpec.create("b")) + + assertThat(loadForUser(userId).toSet()).containsExactlyElementsIn(specs) + } + + @Test + fun markAdded_multipleUsers() = + testScope.runTest { + underTest.markTileAdded(userId = 1, TileSpec.create("a")) + + assertThat(loadForUser(0).toSet()).isEmpty() + assertThat(loadForUser(1).toSet()) + .containsExactlyElementsIn(setOf(TileSpec.create("a"))) + } + + @Test + fun markAdded_Invalid_noop() = + testScope.runTest { + val userId = 0 + underTest.markTileAdded(userId, TileSpec.Invalid) + + assertThat(loadForUser(userId).toSet()).isEmpty() + } + + @Test + fun unmarkAdded() = + testScope.runTest { + val userId = 0 + val specs = "a,custom(b/c)" + storeForUser(specs, userId) + + underTest.unmarkTileAdded(userId, TileSpec.create("a")) + + assertThat(loadForUser(userId).toSet()) + .containsExactlyElementsIn(setOf(TileSpec.create("custom(b/c)"))) + } + + @Test + fun unmarkAdded_multipleUsers() = + testScope.runTest { + val specs = "a,b" + storeForUser(specs, 0) + storeForUser(specs, 1) + + underTest.unmarkTileAdded(1, TileSpec.create("a")) + + assertThat(loadForUser(0).toSet()).isEqualTo(specs.toSet()) + assertThat(loadForUser(1).toSet()).isEqualTo(setOf(TileSpec.create("b"))) + } + + private fun storeForUser(specs: String, userId: Int) { + secureSettings.putStringForUser(SETTING, specs, userId) + } + + private fun loadForUser(userId: Int): String { + return secureSettings.getStringForUser(SETTING, userId) ?: "" + } + + companion object { + private const val SETTING = Settings.Secure.QS_AUTO_ADDED_TILES + private const val DELIMITER = "," + + fun Set<TileSpec>.toSeparatedString() = joinToString(DELIMITER, transform = TileSpec::spec) + + fun String.toSet(): Set<TileSpec> { + return if (isNullOrBlank()) { + emptySet() + } else { + split(DELIMITER).map(TileSpec::create).toSet() + } + } + } +} 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 5ca37716cbff..068d933652ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -93,6 +93,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerReposito import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository; import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor; +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel; @@ -302,6 +303,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { protected ArgumentCaptor<NotificationStackScrollLayout.OnEmptySpaceClickListener> mEmptySpaceClickListenerCaptor; @Mock protected ActivityStarter mActivityStarter; + @Mock protected KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor; protected KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor; protected KeyguardInteractor mKeyguardInteractor; @@ -599,7 +601,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mDumpManager, mKeyuardLongPressViewModel, mKeyguardInteractor, - mActivityStarter); + mActivityStarter, + mKeyguardFaceAuthInteractor); mNotificationPanelViewController.initDependencies( mCentralSurfaces, null, @@ -665,7 +668,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mMetricsLogger, mFeatureFlags, mInteractionJankMonitor, - mShadeLog + mShadeLog, + mKeyguardFaceAuthInteractor ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 2db9c9788bf8..600fb5c2e1bc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -930,6 +930,7 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo mTouchHandler.onTouch(mock(View.class), mDownMotionEvent); mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0); + verify(mKeyguardFaceAuthInteractor).onNotificationPanelClicked(); verify(mUpdateMonitor).requestFaceAuth( FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java index d8ffe39e427d..908f7cbf4801 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java @@ -62,6 +62,7 @@ import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.fragments.FragmentHostManager; +import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor; import com.android.systemui.media.controls.pipeline.MediaDataManager; import com.android.systemui.media.controls.ui.MediaHierarchyManager; import com.android.systemui.plugins.FalsingManager; @@ -239,7 +240,8 @@ public class QuickSettingsControllerTest extends SysuiTestCase { mMetricsLogger, mFeatureFlags, mInteractionJankMonitor, - mShadeLogger + mShadeLogger, + mock(KeyguardFaceAuthInteractor.class) ); mFragmentListener = mQsController.getQsFragmentListener(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt index b043e97f1054..76f7401adbb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeHeaderControllerTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.shade import android.animation.Animator +import android.app.AlarmManager +import android.app.PendingIntent import android.app.StatusBarManager import android.content.Context import android.content.res.Resources @@ -40,8 +42,10 @@ import com.android.systemui.battery.BatteryMeterViewController import com.android.systemui.demomode.DemoMode import com.android.systemui.demomode.DemoModeController import com.android.systemui.dump.DumpManager +import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.ChipVisibilityListener import com.android.systemui.qs.HeaderPrivacyIconsController +import com.android.systemui.shade.ShadeHeaderController.Companion.DEFAULT_CLOCK_INTENT import com.android.systemui.shade.ShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT import com.android.systemui.shade.ShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT import com.android.systemui.shade.ShadeHeaderController.Companion.QS_HEADER_CONSTRAINT @@ -52,6 +56,7 @@ import com.android.systemui.statusbar.phone.StatusBarIconController import com.android.systemui.statusbar.phone.StatusIconContainer import com.android.systemui.statusbar.policy.Clock import com.android.systemui.statusbar.policy.FakeConfigurationController +import com.android.systemui.statusbar.policy.NextAlarmController import com.android.systemui.statusbar.policy.VariableDateView import com.android.systemui.statusbar.policy.VariableDateViewController import com.android.systemui.util.mockito.any @@ -114,6 +119,8 @@ class ShadeHeaderControllerTest : SysuiTestCase() { @Mock private lateinit var demoModeController: DemoModeController @Mock private lateinit var qsBatteryModeController: QsBatteryModeController + @Mock private lateinit var nextAlarmController: NextAlarmController + @Mock private lateinit var activityStarter: ActivityStarter @JvmField @Rule val mockitoRule = MockitoJUnit.rule() var viewVisibility = View.GONE @@ -181,6 +188,8 @@ class ShadeHeaderControllerTest : SysuiTestCase() { combinedShadeHeadersConstraintManager, demoModeController, qsBatteryModeController, + nextAlarmController, + activityStarter, ) whenever(view.isAttachedToWindow).thenReturn(true) shadeHeaderController.init() @@ -828,6 +837,28 @@ class ShadeHeaderControllerTest : SysuiTestCase() { verify(carrierGroup).setPaddingRelative(514, 0, 0, 0) } + @Test + fun launchClock_launchesDefaultIntentWhenNoAlarmSet() { + shadeHeaderController.launchClockActivity() + + verify(activityStarter).postStartActivityDismissingKeyguard(DEFAULT_CLOCK_INTENT, 0) + } + + @Test + fun launchClock_launchesNextAlarmWhenExists() { + val pendingIntent = mock<PendingIntent>() + val aci = AlarmManager.AlarmClockInfo(12345, pendingIntent) + val captor = + ArgumentCaptor.forClass(NextAlarmController.NextAlarmChangeCallback::class.java) + + verify(nextAlarmController).addCallback(capture(captor)) + captor.value.onNextAlarmChanged(aci) + + shadeHeaderController.launchClockActivity() + + verify(activityStarter).postStartActivityDismissingKeyguard(pendingIntent) + } + private fun View.executeLayoutChange( left: Int, top: Int, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt index 8109e24a1e52..c2a2a40b7e5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt @@ -25,6 +25,10 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.advanceTimeBy import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.TransitionState +import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.notification.NotifPipelineFlags @@ -69,6 +73,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() { private val headsUpManager: HeadsUpManager = mock() private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock() private val keyguardRepository = FakeKeyguardRepository() + private val keyguardTransitionRepository = FakeKeyguardTransitionRepository() private val notifPipelineFlags: NotifPipelineFlags = mock() private val notifPipeline: NotifPipeline = mock() private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock() @@ -118,6 +123,33 @@ class KeyguardCoordinatorTest : SysuiTestCase() { } @Test + fun unseenFilterStopsMarkingSeenNotifWhenTransitionToAod() { + whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true) + + // GIVEN: Keyguard is not showing, shade is not expanded, and a notification is present + keyguardRepository.setKeyguardShowing(false) + whenever(statusBarStateController.isExpanded).thenReturn(false) + runKeyguardCoordinatorTest { + val fakeEntry = NotificationEntryBuilder().build() + collectionListener.onEntryAdded(fakeEntry) + + // WHEN: The device transitions to AOD + keyguardTransitionRepository.sendTransitionStep( + TransitionStep(to = KeyguardState.AOD, transitionState = TransitionState.STARTED), + ) + testScheduler.runCurrent() + + // WHEN: The shade is expanded + whenever(statusBarStateController.isExpanded).thenReturn(true) + statusBarStateListener.onExpandedChanged(true) + testScheduler.runCurrent() + + // THEN: The notification is still treated as "unseen" and is not filtered out. + assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse() + } + } + + @Test fun unseenFilter_headsUpMarkedAsSeen() { whenever(notifPipelineFlags.shouldFilterUnseenNotifsOnKeyguard).thenReturn(true) @@ -373,6 +405,7 @@ class KeyguardCoordinatorTest : SysuiTestCase() { headsUpManager, keyguardNotifVisibilityProvider, keyguardRepository, + keyguardTransitionRepository, notifPipelineFlags, testScope.backgroundScope, sectionHeaderVisibilityProvider, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 48710a42f616..63320693831c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -1278,12 +1278,13 @@ public class CentralSurfacesImplTest extends SysuiTestCase { new Intent(), /* onlyProvisioned = */false, /* dismissShade = */false); - verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any(Runnable.class)); ArgumentCaptor<OnDismissAction> onDismissActionCaptor = ArgumentCaptor.forClass(OnDismissAction.class); verify(mStatusBarKeyguardViewManager) - .dismissWithAction(onDismissActionCaptor.capture(), any(Runnable.class), eq(true)); + .dismissWithAction(onDismissActionCaptor.capture(), any(Runnable.class), eq(true), + eq(null)); assertThat(onDismissActionCaptor.getValue().onDismiss()).isFalse(); + verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any(Runnable.class)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java index eb0b9b3a3fb1..760a90b4d59a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java @@ -54,6 +54,7 @@ import com.android.systemui.battery.BatteryMeterViewController; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.statusbar.CommandQueue; +import com.android.systemui.statusbar.NotificationMediaManager; import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.policy.BatteryController; @@ -120,6 +121,8 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { @Mock private CommandQueue mCommandQueue; @Mock private KeyguardLogger mLogger; + @Mock private NotificationMediaManager mNotificationMediaManager; + private TestNotificationPanelViewStateProvider mNotificationPanelViewStateProvider; private KeyguardStatusBarView mKeyguardStatusBarView; private KeyguardStatusBarViewController mController; @@ -167,7 +170,8 @@ public class KeyguardStatusBarViewControllerTest extends SysuiTestCase { mSecureSettings, mCommandQueue, mFakeExecutor, - mLogger + mLogger, + mNotificationMediaManager ); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt index a2828d33375b..1cc0bd3cb36c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt @@ -25,7 +25,6 @@ import android.testing.AndroidTestingRunner import android.view.IWindowManager import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.dump.DumpManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock @@ -52,7 +51,6 @@ class LetterboxBackgroundProviderTest : SysuiTestCase() { @get:Rule var expect: Expect = Expect.create() @Mock private lateinit var windowManager: IWindowManager - @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var wallpaperManager: WallpaperManager private lateinit var provider: LetterboxBackgroundProvider @@ -65,8 +63,7 @@ class LetterboxBackgroundProviderTest : SysuiTestCase() { setUpWallpaperManager() provider = - LetterboxBackgroundProvider( - windowManager, fakeExecutor, dumpManager, wallpaperManager, mainHandler) + LetterboxBackgroundProvider(windowManager, fakeExecutor, wallpaperManager, mainHandler) } private fun setUpWallpaperManager() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java index 6c0f6c2f65a5..07ffd112300b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemUIDialogTest.java @@ -14,6 +14,8 @@ package com.android.systemui.statusbar.phone; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; @@ -44,6 +46,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.concurrent.atomic.AtomicBoolean; + @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest @@ -109,4 +113,31 @@ public class SystemUIDialogTest extends SysuiTestCase { dialog.dismiss(); assertFalse(dialog.isShowing()); } + + @Test public void startAndStopAreCalled() { + AtomicBoolean calledStart = new AtomicBoolean(false); + AtomicBoolean calledStop = new AtomicBoolean(false); + SystemUIDialog dialog = new SystemUIDialog(mContext) { + @Override + protected void start() { + calledStart.set(true); + } + + @Override + protected void stop() { + calledStop.set(true); + } + }; + + assertThat(calledStart.get()).isFalse(); + assertThat(calledStop.get()).isFalse(); + + dialog.show(); + assertThat(calledStart.get()).isTrue(); + assertThat(calledStop.get()).isFalse(); + + dialog.dismiss(); + assertThat(calledStart.get()).isTrue(); + assertThat(calledStop.get()).isTrue(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index 746c92e485b7..02c459b11d07 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -16,12 +16,10 @@ package com.android.systemui.statusbar.phone -import android.animation.Animator import android.os.Handler import android.os.PowerManager import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper -import android.view.View import androidx.test.filters.SmallTest import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.SysuiTestCase @@ -39,10 +37,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Mock -import org.mockito.Mockito import org.mockito.Mockito.anyLong import org.mockito.Mockito.never -import org.mockito.Mockito.spy import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @@ -111,27 +107,6 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { controller.onStartedWakingUp() } - @Test - fun testAnimClearsEndListener() { - val keyguardView = View(context) - val animator = spy(keyguardView.animate()) - val keyguardSpy = spy(keyguardView) - Mockito.`when`(keyguardSpy.animate()).thenReturn(animator) - val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java) - val endAction = ArgumentCaptor.forClass(Runnable::class.java) - controller.animateInKeyguard(keyguardSpy, Runnable {}) - Mockito.verify(animator).setListener(listener.capture()) - Mockito.verify(animator).withEndAction(endAction.capture()) - - // Verify that the listener is cleared if we cancel it. - listener.value.onAnimationCancel(null) - Mockito.verify(animator).setListener(null) - - // Verify that the listener is also cleared if the end action is triggered. - endAction.value.run() - verify(animator, times(2)).setListener(null) - } - /** * The AOD UI is shown during the screen off animation, after a delay to allow the light reveal * animation to start. If the device is woken up during the screen off, we should *never* do diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt index 3ced7b2c4e6e..b2bbcfd3d6ef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt @@ -49,6 +49,8 @@ class FakeMobileIconsInteractor( FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G, ) + private val interactorCache: MutableMap<Int, FakeMobileIconInteractor> = mutableMapOf() + override val isDefaultConnectionFailed = MutableStateFlow(false) override val filteredSubscriptions = MutableStateFlow<List<SubscriptionModel>>(listOf()) @@ -75,7 +77,15 @@ class FakeMobileIconsInteractor( /** Always returns a new fake interactor */ override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor { - return FakeMobileIconInteractor(tableLogBuffer) + return FakeMobileIconInteractor(tableLogBuffer).also { interactorCache[subId] = it } + } + + /** + * Returns the most recently created interactor for the given subId, or null if an interactor + * has never been created for that sub. + */ + fun getInteractorForSubId(subId: Int): FakeMobileIconInteractor? { + return interactorCache[subId] } companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt index 01bec879102d..f8e1aa94c387 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel import androidx.test.filters.SmallTest +import com.android.settingslib.mobile.TelephonyIcons import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.phone.StatusBarLocation import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags @@ -24,6 +25,7 @@ import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirp import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconsInteractor +import com.android.systemui.statusbar.pipeline.mobile.domain.model.NetworkTypeIconModel import com.android.systemui.statusbar.pipeline.mobile.ui.MobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy @@ -135,8 +137,179 @@ class MobileIconsViewModelTest : SysuiTestCase() { assertThat(underTest.mobileIconSubIdCache).containsExactly(2, model2.commonImpl) } + @Test + fun firstMobileSubShowingNetworkTypeIcon_noSubs_false() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = emptyList() + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_oneSub_notShowingRat_false() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1) + // The unknown icon group doesn't show a RAT + interactor.getInteractorForSubId(1)!!.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_oneSub_showingRat_true() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1) + // The 3G icon group will show a RAT + interactor.getInteractorForSubId(1)!!.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_updatesAsSubUpdates() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1) + val sub1Interactor = interactor.getInteractorForSubId(1)!! + + sub1Interactor.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + assertThat(latest).isTrue() + + sub1Interactor.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + assertThat(latest).isFalse() + + sub1Interactor.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.LTE) + assertThat(latest).isTrue() + + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubNotShowingRat_false() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_multipleSubs_lastSubShowingRat_true() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + + assertThat(latest).isTrue() + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_subListUpdates_valAlsoUpdates() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + interactor.getInteractorForSubId(1)?.networkTypeIconGroup?.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + interactor.getInteractorForSubId(2)!!.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + + assertThat(latest).isTrue() + + // WHEN the sub list gets new subscriptions where the last subscription is not showing + // the network type icon + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2, SUB_3) + interactor.getInteractorForSubId(3)!!.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + + // THEN the flow updates + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun firstMobileSubShowingNetworkTypeIcon_subListReorders_valAlsoUpdates() = + testScope.runTest { + var latest: Boolean? = null + val job = + underTest.firstMobileSubShowingNetworkTypeIcon.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + // Immediately switch the order so that we've created both interactors + interactor.filteredSubscriptions.value = listOf(SUB_2, SUB_1) + val sub1Interactor = interactor.getInteractorForSubId(1)!! + val sub2Interactor = interactor.getInteractorForSubId(2)!! + + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + sub1Interactor.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.UNKNOWN) + sub2Interactor.networkTypeIconGroup.value = + NetworkTypeIconModel.DefaultIcon(TelephonyIcons.THREE_G) + assertThat(latest).isTrue() + + // WHEN sub1 becomes last and sub1 has no network type icon + interactor.filteredSubscriptions.value = listOf(SUB_2, SUB_1) + + // THEN the flow updates + assertThat(latest).isFalse() + + // WHEN sub2 becomes last and sub2 has a network type icon + interactor.filteredSubscriptions.value = listOf(SUB_1, SUB_2) + + // THEN the flow updates + assertThat(latest).isTrue() + + job.cancel() + } + companion object { private val SUB_1 = SubscriptionModel(subscriptionId = 1, isOpportunistic = false) private val SUB_2 = SubscriptionModel(subscriptionId = 2, isOpportunistic = false) + private val SUB_3 = SubscriptionModel(subscriptionId = 3, isOpportunistic = false) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt index 5c19108cc17e..0d51af2754f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt @@ -50,6 +50,7 @@ import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -98,12 +99,12 @@ class ModernStatusBarWifiViewTest : SysuiTestCase() { val viewModelCommon = WifiViewModel( airplaneModeViewModel, + shouldShowSignalSpacerProvider = { MutableStateFlow(false) }, connectivityConstants, context, tableLogBuffer, interactor, scope, - statusBarPipelineFlags, wifiConstants, ) viewModel = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt index ffe990bf1cf6..e6724d86ec55 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelIconParameterizedTest.kt @@ -27,7 +27,6 @@ import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK -import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel @@ -46,6 +45,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.runBlocking import kotlinx.coroutines.yield @@ -66,7 +66,6 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase private lateinit var underTest: WifiViewModel - @Mock private lateinit var statusBarPipelineFlags: StatusBarPipelineFlags @Mock private lateinit var tableLogBuffer: TableLogBuffer @Mock private lateinit var connectivityConstants: ConnectivityConstants @Mock private lateinit var wifiConstants: WifiConstants @@ -121,12 +120,12 @@ internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase underTest = WifiViewModel( airplaneModeViewModel, + shouldShowSignalSpacerProvider = { MutableStateFlow(false) }, connectivityConstants, context, tableLogBuffer, interactor, scope, - statusBarPipelineFlags, wifiConstants, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt index 802e360797a4..0e303b244094 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModelTest.kt @@ -39,8 +39,8 @@ import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWi import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.cancel +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking @@ -53,7 +53,6 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest class WifiViewModelTest : SysuiTestCase() { @@ -68,6 +67,7 @@ class WifiViewModelTest : SysuiTestCase() { private lateinit var wifiRepository: FakeWifiRepository private lateinit var interactor: WifiInteractor private lateinit var airplaneModeViewModel: AirplaneModeViewModel + private val shouldShowSignalSpacerProviderFlow = MutableStateFlow(false) private lateinit var scope: CoroutineScope @Before @@ -473,6 +473,34 @@ class WifiViewModelTest : SysuiTestCase() { job.cancel() } + @Test + fun signalSpacer_firstSubNotShowingNetworkTypeIcon_outputsFalse() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isSignalSpacerVisible.onEach { latest = it }.launchIn(this) + + shouldShowSignalSpacerProviderFlow.value = false + yield() + + assertThat(latest).isFalse() + + job.cancel() + } + + @Test + fun signalSpacer_firstSubIsShowingNetworkTypeIcon_outputsTrue() = + runBlocking(IMMEDIATE) { + var latest: Boolean? = null + val job = underTest.isSignalSpacerVisible.onEach { latest = it }.launchIn(this) + + shouldShowSignalSpacerProviderFlow.value = true + yield() + + assertThat(latest).isTrue() + + job.cancel() + } + private fun createAndSetViewModel() { // [WifiViewModel] creates its flows as soon as it's instantiated, and some of those flow // creations rely on certain config values that we mock out in individual tests. This method @@ -480,12 +508,12 @@ class WifiViewModelTest : SysuiTestCase() { underTest = WifiViewModel( airplaneModeViewModel, + { shouldShowSignalSpacerProviderFlow }, connectivityConstants, context, tableLogBuffer, interactor, scope, - statusBarPipelineFlags, wifiConstants, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index eb2688894cb0..e33bfd7d601e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -58,6 +58,7 @@ import com.android.systemui.util.DeviceConfigProxyFake; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -370,6 +371,13 @@ public class VolumeDialogImplTest extends SysuiTestCase { verify(mCsdWarningDialog).show(); } + @After + public void teardown() { + if (mDialog != null) { + mDialog.clearInternalHandleAfterTest(); + } + } + /* @Test public void testContentDescriptions() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index e82456524012..a42acd3464ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -1257,6 +1257,7 @@ public class BubblesTest extends SysuiTestCase { stackView.showManageMenu(true); assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); assertTrue(stackView.isManageMenuSettingsVisible()); + assertTrue(stackView.isManageMenuDontBubbleVisible()); } @Test @@ -1274,6 +1275,7 @@ public class BubblesTest extends SysuiTestCase { stackView.showManageMenu(true); assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */); assertFalse(stackView.isManageMenuSettingsVisible()); + assertFalse(stackView.isManageMenuDontBubbleVisible()); } @Test diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java index 1bab99787de4..1ec4e8c48707 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java @@ -22,12 +22,14 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.app.Instrumentation; +import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; import android.os.ParcelFileDescriptor; import android.testing.DexmakerShareClassLoaderRule; import android.testing.LeakCheck; +import android.testing.TestWithLooperRule; import android.testing.TestableLooper; import android.util.Log; @@ -73,12 +75,21 @@ public abstract class SysuiTestCase { @Rule public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule = new DexmakerShareClassLoaderRule(); + + // set the highest order so it's the innermost rule + @Rule(order = Integer.MAX_VALUE) + public TestWithLooperRule mlooperRule = new TestWithLooperRule(); + public TestableDependency mDependency; private Instrumentation mRealInstrumentation; private FakeBroadcastDispatcher mFakeBroadcastDispatcher; @Before public void SysuiSetup() throws Exception { + // Manually associate a Display to context for Robolectric test. Similar to b/214297409 + if (isRobolectricTest()) { + mContext = mContext.createDefaultDisplayContext(); + } SystemUIInitializer initializer = SystemUIInitializerFactory.createFromConfigNoAssert(mContext); initializer.init(true); @@ -215,6 +226,10 @@ public abstract class SysuiTestCase { idler.waitForIdle(); } + public static boolean isRobolectricTest() { + return Build.FINGERPRINT.contains("robolectric"); + } + private static final void validateThread(Looper l) { if (Looper.myLooper() == l) { throw new RuntimeException( diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java index 0674ea855d7f..5ff57aad9f5d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java @@ -18,6 +18,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.hardware.display.DisplayManager; import android.os.Handler; import android.os.UserHandle; import android.testing.LeakCheck; @@ -56,6 +57,11 @@ public class SysuiTestableContext extends TestableContext { return context; } + public SysuiTestableContext createDefaultDisplayContext() { + Display display = getBaseContext().getSystemService(DisplayManager.class).getDisplays()[0]; + return (SysuiTestableContext) createDisplayContext(display); + } + public void cleanUpReceivers(String testName) { Set<BroadcastReceiver> copy; synchronized (mRegisteredReceivers) { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt index c08ecd0e3b0c..738f09ddce3d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt @@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.filterNotNull -import kotlinx.coroutines.flow.map class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository { @@ -46,14 +45,19 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository { private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null) val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> = _runningAuthRequest.asStateFlow() - override val isAuthRunning = _runningAuthRequest.map { it != null } + + private val _isAuthRunning = MutableStateFlow(false) + override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning + override val isBypassEnabled = MutableStateFlow(false) override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) { _runningAuthRequest.value = uiEvent to fallbackToDetection + _isAuthRunning.value = true } override fun cancel() { + _isAuthRunning.value = false _runningAuthRequest.value = null } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/widget/FakeListAdapter.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/widget/FakeListAdapter.kt new file mode 100644 index 000000000000..231373b50c47 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/widget/FakeListAdapter.kt @@ -0,0 +1,47 @@ +/* + * 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.widget + +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter + +class FakeListAdapter(private var items: List<FakeListAdapterItem> = emptyList()) : BaseAdapter() { + + fun setItems(items: List<FakeListAdapterItem>) { + this.items = items + notifyDataSetChanged() + } + + override fun getCount(): Int = items.size + + override fun getItem(position: Int): Any = items[position].data + + override fun getItemId(position: Int): Long = items[position].id + + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View = + items[position].view(position, convertView, parent) + + class FakeListAdapterItem( + /** Result returned in [Adapter#getView] */ + val view: (position: Int, convertView: View?, parent: ViewGroup?) -> View, + /** Returned in [Adapter#getItemId] */ + val id: Long = 0, + /** Returned in [Adapter#getItem] */ + val data: Any = Unit, + ) +} diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt index 0b019d1285e3..30418883eaf8 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt @@ -35,7 +35,7 @@ class UnfoldRemoteFilter( private var inProgress = false - private var processedProgress: Float = 0.0f + private var processedProgress: Float = 1.0f set(newProgress) { if (inProgress) { logCounter({ "$TAG#filtered_progress" }, newProgress) diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 51325e72204d..0bdb0c80d219 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -112,6 +112,7 @@ import android.util.IntArray; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; +import android.util.SparseBooleanArray; import android.view.Display; import android.view.IWindow; import android.view.InputDevice; @@ -160,6 +161,7 @@ import com.android.server.accessibility.magnification.WindowMagnificationManager import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.pm.UserManagerInternal; import com.android.server.policy.WindowManagerPolicy; +import com.android.server.utils.Slogf; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerInternal; import com.android.settingslib.RestrictedLockUtils; @@ -301,7 +303,23 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private final List<SendWindowStateChangedEventRunnable> mSendWindowStateChangedEventRunnables = new ArrayList<>(); - private int mCurrentUserId = UserHandle.USER_SYSTEM; + @GuardedBy("mLock") + private @UserIdInt int mCurrentUserId = UserHandle.USER_SYSTEM; + + // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation: + // when the UiAutomation is set in a visible background user, mCurrentUserId points to that user + // and mRealCurrentUserId points to the "real" current user; otherwise, mRealCurrentUserId + // is set as UserHandle.USER_CURRENT. + @GuardedBy("mLock") + private @UserIdInt int mRealCurrentUserId = UserHandle.USER_CURRENT; + + // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation + // purposes - in the long term, the whole service should be refactored so it handles "visible" + // users, not current user. Notice that because this is temporary, it's not trying to optimize + // performance / utilization (for example, it's not using an IntArray) + @GuardedBy("mLock") + @Nullable // only set when device supports visible background users + private final SparseBooleanArray mVisibleBgUserIds; //TODO: Remove this hack private boolean mInitialized; @@ -316,6 +334,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private SparseArray<SurfaceControl> mA11yOverlayLayers = new SparseArray<>(); private final FlashNotificationsController mFlashNotificationsController; + private final UserManagerInternal mUmi; private AccessibilityUserState getCurrentUserStateLocked() { return getUserStateLocked(mCurrentUserId); @@ -445,6 +464,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mHasInputFilter = true; } mFlashNotificationsController = new FlashNotificationsController(mContext); + mUmi = LocalServices.getService(UserManagerInternal.class); + // TODO(b/255426725): not used on tests + mVisibleBgUserIds = null; + init(); } @@ -477,6 +500,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub mProxyManager = new ProxyManager(mLock, mA11yWindowManager, mContext, mMainHandler, mUiAutomationManager, this); mFlashNotificationsController = new FlashNotificationsController(mContext); + mUmi = LocalServices.getService(UserManagerInternal.class); + + if (UserManager.isVisibleBackgroundUsersEnabled()) { + mVisibleBgUserIds = new SparseBooleanArray(); + mUmi.addUserVisibilityListener((u, v) -> onUserVisibilityChanged(u, v)); + } else { + mVisibleBgUserIds = null; + } + init(); } @@ -493,6 +525,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub return mCurrentUserId; } + @GuardedBy("mLock") + @Override + public SparseBooleanArray getVisibleUserIdsLocked() { + return mVisibleBgUserIds; + } + @Override public boolean isAccessibilityButtonShown() { return mIsAccessibilityButtonShown; @@ -1362,6 +1400,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub public void registerUiTestAutomationService(IBinder owner, IAccessibilityServiceClient serviceClient, AccessibilityServiceInfo accessibilityServiceInfo, + int userId, int flags) { if (mTraceManager.isA11yTracingEnabledForTypes(FLAGS_ACCESSIBILITY_MANAGER)) { mTraceManager.logTrace(LOG_TAG + ".registerUiTestAutomationService", @@ -1374,6 +1413,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE); synchronized (mLock) { + changeCurrentUserForTestAutomationIfNeededLocked(userId); mUiAutomationManager.registerUiTestAutomationServiceLocked(owner, serviceClient, mContext, accessibilityServiceInfo, sIdCounter++, mMainHandler, mSecurityPolicy, this, getTraceManager(), mWindowManagerService, @@ -1390,7 +1430,47 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } synchronized (mLock) { mUiAutomationManager.unregisterUiTestAutomationServiceLocked(serviceClient); + restoreCurrentUserAfterTestAutomationIfNeededLocked(); + } + } + + // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation + @GuardedBy("mLock") + private void changeCurrentUserForTestAutomationIfNeededLocked(@UserIdInt int userId) { + if (mVisibleBgUserIds == null) { + Slogf.d(LOG_TAG, "changeCurrentUserForTestAutomationIfNeededLocked(%d): ignoring " + + "because device doesn't support visible background users", userId); + return; + } + if (!mVisibleBgUserIds.get(userId)) { + Slogf.wtf(LOG_TAG, "Cannot change current user to %d as it's not visible " + + "(mVisibleUsers=%s)", userId, mVisibleBgUserIds); + return; + } + if (mCurrentUserId == userId) { + Slogf.w(LOG_TAG, "NOT changing current user for test automation purposes as it is " + + "already %d", mCurrentUserId); + return; + } + Slogf.i(LOG_TAG, "Changing current user from %d to %d for test automation purposes", + mCurrentUserId, userId); + mRealCurrentUserId = mCurrentUserId; + switchUser(userId); + } + + // TODO(b/255426725): temporary workaround to support visible background users for UiAutomation + @GuardedBy("mLock") + private void restoreCurrentUserAfterTestAutomationIfNeededLocked() { + if (mVisibleBgUserIds == null) { + Slogf.d(LOG_TAG, "restoreCurrentUserForTestAutomationIfNeededLocked(): ignoring " + + "because device doesn't support visible background users"); + return; } + Slogf.i(LOG_TAG, "Restoring current user to %d after using %d for test automation purposes", + mRealCurrentUserId, mCurrentUserId); + int currentUserId = mRealCurrentUserId; + mRealCurrentUserId = UserHandle.USER_CURRENT; + switchUser(currentUserId); } @Override @@ -2291,8 +2371,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private void updateServicesLocked(AccessibilityUserState userState) { Map<ComponentName, AccessibilityServiceConnection> componentNameToServiceMap = userState.mComponentNameToServiceMap; - boolean isUnlockingOrUnlocked = LocalServices.getService(UserManagerInternal.class) - .isUserUnlockingOrUnlocked(userState.mUserId); + boolean isUnlockingOrUnlocked = mUmi.isUserUnlockingOrUnlocked(userState.mUserId); for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) { AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i); @@ -2593,6 +2672,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } + private void onUserVisibilityChanged(@UserIdInt int userId, boolean visible) { + if (DEBUG) { + Slogf.d(LOG_TAG, "onUserVisibilityChanged(): %d => %b", userId, visible); + } + synchronized (mLock) { + if (visible) { + mVisibleBgUserIds.put(userId, visible); + } else { + mVisibleBgUserIds.delete(userId); + } + } + } + /** * Called when any property of the user state has changed. * @@ -4025,7 +4117,16 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)"); pw.println(); pw.append("currentUserId=").append(String.valueOf(mCurrentUserId)); + if (mRealCurrentUserId != UserHandle.USER_CURRENT + && mCurrentUserId != mRealCurrentUserId) { + pw.append(" (set for UiAutomation purposes; \"real\" current user is ") + .append(String.valueOf(mRealCurrentUserId)).append(")"); + } pw.println(); + if (mVisibleBgUserIds != null) { + pw.append("visibleBgUserIds=").append(mVisibleBgUserIds.toString()); + pw.println(); + } pw.append("hasWindowMagnificationConnection=").append( String.valueOf(getWindowMagnificationMgr().isConnected())); pw.println(); @@ -4052,6 +4153,7 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } pw.println(); mProxyManager.dump(fd, pw, args); + mA11yDisplayListener.dump(fd, pw, args); } } @@ -4437,6 +4539,20 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub /* do nothing */ } + void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("Accessibility Display Listener:"); + pw.println(" SystemUI uid: " + mSystemUiUid); + int size = mDisplaysList.size(); + pw.printf(" %d valid display%s: ", size, (size == 1 ? "" : "s")); + for (int i = 0; i < size; i++) { + pw.print(mDisplaysList.get(i).getDisplayId()); + if (i < size - 1) { + pw.print(", "); + } + } + pw.println(); + } + private boolean isValidDisplay(@Nullable Display display) { if (display == null || display.getType() == Display.TYPE_OVERLAY) { return false; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java index c37ea501bbc9..88656239e59b 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java @@ -39,6 +39,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.ArraySet; import android.util.Slog; +import android.util.SparseBooleanArray; import android.view.accessibility.AccessibilityEvent; import android.view.inputmethod.InputMethodInfo; @@ -88,6 +89,12 @@ public class AccessibilitySecurityPolicy { */ int getCurrentUserIdLocked(); // TODO: Should include resolveProfileParentLocked, but that was already in SecurityPolicy + + // TODO(b/255426725): temporary hack; see comment on A11YMS.mVisibleBgUserIds + /** + * Returns the {@link android.os.UserManager#getVisibleUsers() visible users}. + */ + @Nullable SparseBooleanArray getVisibleUserIdsLocked(); } private final Context mContext; diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java index baed181ebd43..a8a536590004 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityWindowManager.java @@ -51,6 +51,7 @@ import android.view.accessibility.IAccessibilityInteractionConnection; import com.android.internal.annotations.VisibleForTesting; import com.android.server.accessibility.AccessibilitySecurityPolicy.AccessibilityUserManager; +import com.android.server.utils.Slogf; import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; @@ -59,6 +60,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; /** * This class provides APIs for accessibility manager to manage {@link AccessibilityWindowInfo}s and @@ -67,6 +69,7 @@ import java.util.List; public class AccessibilityWindowManager { private static final String LOG_TAG = "AccessibilityWindowManager"; private static final boolean DEBUG = false; + private static final boolean VERBOSE = false; private static int sNextWindowId; @@ -209,6 +212,9 @@ public class AccessibilityWindowManager { * Constructor for DisplayWindowsObserver. */ DisplayWindowsObserver(int displayId) { + if (DEBUG) { + Slogf.d(LOG_TAG, "Creating DisplayWindowsObserver for displayId %d", displayId); + } mDisplayId = displayId; } @@ -430,12 +436,27 @@ public class AccessibilityWindowManager { synchronized (mLock) { updateWindowsByWindowAttributesLocked(windows); if (DEBUG) { - Slog.i(LOG_TAG, "Display Id = " + mDisplayId); - Slog.i(LOG_TAG, "Windows changed: " + windows); + Slogf.i(LOG_TAG, "mDisplayId=%d, topFocusedDisplayId=%d, currentUserId=%d, " + + "visibleBgUsers=%s", mDisplayId, topFocusedDisplayId, + mAccessibilityUserManager.getCurrentUserIdLocked(), + mAccessibilityUserManager.getVisibleUserIdsLocked()); + if (VERBOSE) { + Slogf.i(LOG_TAG, "%d windows changed: %s ", windows.size(), windows); + } else { + List<String> windowsInfo = windows.stream() + .map(w -> "{displayId=" + w.displayId + ", title=" + w.title + "}") + .collect(Collectors.toList()); + Slogf.i(LOG_TAG, "%d windows changed: %s", windows.size(), windowsInfo); + } } if (shouldUpdateWindowsLocked(forceSend, windows)) { mTopFocusedDisplayId = topFocusedDisplayId; mTopFocusedWindowToken = topFocusedWindowToken; + if (DEBUG) { + Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): updating windows for " + + "display %d and token %s", + topFocusedDisplayId, topFocusedWindowToken); + } cacheWindows(windows); // Lets the policy update the focused and active windows. updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(), @@ -443,6 +464,11 @@ public class AccessibilityWindowManager { // Someone may be waiting for the windows - advertise it. mLock.notifyAll(); } + else if (DEBUG) { + Slogf.d(LOG_TAG, "onWindowsForAccessibilityChanged(): NOT updating windows for " + + "display %d and token %s", + topFocusedDisplayId, topFocusedWindowToken); + } } } @@ -472,6 +498,12 @@ public class AccessibilityWindowManager { } final int windowCount = windows.size(); + if (VERBOSE) { + Slogf.v(LOG_TAG, + "shouldUpdateWindowsLocked(): mDisplayId=%d, windowCount=%d, " + + "mCachedWindowInfos.size()=%d, windows.size()=%d", mDisplayId, + windowCount, mCachedWindowInfos.size(), windows.size()); + } // We computed the windows and if they changed notify the client. if (mCachedWindowInfos.size() != windowCount) { // Different size means something changed. @@ -1274,7 +1306,7 @@ public class AccessibilityWindowManager { */ @Nullable public RemoteAccessibilityConnection getConnectionLocked(int userId, int windowId) { - if (DEBUG) { + if (VERBOSE) { Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId); } RemoteAccessibilityConnection connection = mGlobalInteractionConnections.get(windowId); diff --git a/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java index 1990fe277af9..98aebddddac9 100644 --- a/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java +++ b/services/backup/java/com/android/server/backup/BackupAndRestoreFeatureFlags.java @@ -77,4 +77,19 @@ public class BackupAndRestoreFeatureFlags { /* name= */ "full_backup_utils_route_buffer_size_bytes", /* defaultValue= */ 32 * 1024); // 32 KB } + + /** + * Retrieves the value of the flag + * "unified_restore_continue_after_transport_failure_in_kv_restore". + * If true, Unified restore task will continue to next package if key-value restore of a + * package fails due to Transport-level failure. See b/128499560 for more context. + */ + @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) + public static boolean getUnifiedRestoreContinueAfterTransportFailureInKvRestore() { + return DeviceConfig.getBoolean( + NAMESPACE, + /* name= */ + "unified_restore_continue_after_transport_failure_in_kv_restore", + /* defaultValue= */ true); + } } diff --git a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java index 18e28de75782..1656b6f0ab9b 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformUnifiedRestoreTask.java @@ -57,6 +57,7 @@ import com.android.server.AppWidgetBackupBridge; import com.android.server.EventLogTags; import com.android.server.LocalServices; import com.android.server.backup.BackupAgentTimeoutParameters; +import com.android.server.backup.BackupAndRestoreFeatureFlags; import com.android.server.backup.BackupRestoreTask; import com.android.server.backup.BackupUtils; import com.android.server.backup.OperationStorage; @@ -168,11 +169,13 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { private final BackupEligibilityRules mBackupEligibilityRules; @VisibleForTesting - PerformUnifiedRestoreTask(UserBackupManagerService backupManagerService) { + PerformUnifiedRestoreTask( + UserBackupManagerService backupManagerService, + TransportConnection transportConnection) { mListener = null; mAgentTimeoutParameters = null; mOperationStorage = null; - mTransportConnection = null; + mTransportConnection = transportConnection; mTransportManager = null; mEphemeralOpToken = 0; mUserId = 0; @@ -731,13 +734,18 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { ParcelFileDescriptor.MODE_TRUNCATE); if (transport.getRestoreData(stage) != BackupTransport.TRANSPORT_OK) { - // Transport-level failure, so we wind everything up and - // terminate the restore operation. + // Transport-level failure. This failure could be specific to package currently in + // restore. Slog.e(TAG, "Error getting restore data for " + packageName); EventLog.writeEvent(EventLogTags.RESTORE_TRANSPORT_FAILURE); stage.close(); downloadFile.delete(); - executeNextState(UnifiedRestoreState.FINAL); + UnifiedRestoreState nextState = + BackupAndRestoreFeatureFlags + .getUnifiedRestoreContinueAfterTransportFailureInKvRestore() + ? UnifiedRestoreState.RUNNING_QUEUE + : UnifiedRestoreState.FINAL; + executeNextState(nextState); return; } @@ -1358,6 +1366,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { executeNextState(UnifiedRestoreState.RUNNING_QUEUE); } + @VisibleForTesting void executeNextState(UnifiedRestoreState nextState) { if (MORE_DEBUG) { Slog.i(TAG, " => executing next step on " @@ -1369,6 +1378,26 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask { backupManagerService.getBackupHandler().sendMessage(msg); } + @VisibleForTesting + UnifiedRestoreState getCurrentUnifiedRestoreStateForTesting() { + return mState; + } + + @VisibleForTesting + void setCurrentUnifiedRestoreStateForTesting(UnifiedRestoreState state) { + mState = state; + } + + @VisibleForTesting + void setStateDirForTesting(File stateDir) { + mStateDir = stateDir; + } + + @VisibleForTesting + void initiateOneRestoreForTesting(PackageInfo app, long appVersionCode) { + initiateOneRestore(app, appVersionCode); + } + // restore observer support void sendStartRestore(int numPackages) { if (mObserver != null) { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 5b320a87d113..d2c41a461d1c 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -899,12 +899,9 @@ public class CompanionDeviceManagerService extends SystemService { public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) throws RemoteException { - enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand"); - final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand( - CompanionDeviceManagerService.this, - mAssociationStore, - mDevicePresenceMonitor); - cmd.exec(this, in, out, err, args, callback, resultReceiver); + new CompanionDeviceShellCommand(CompanionDeviceManagerService.this, mAssociationStore, + mDevicePresenceMonitor, mTransportManager) + .exec(this, in, out, err, args, callback, resultReceiver); } @Override diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index 6889bcd63355..6de3585d8d17 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -22,6 +22,7 @@ import android.os.Binder; import android.os.ShellCommand; import com.android.server.companion.presence.CompanionDevicePresenceMonitor; +import com.android.server.companion.transport.CompanionTransportManager; import java.io.PrintWriter; import java.util.List; @@ -32,13 +33,16 @@ class CompanionDeviceShellCommand extends ShellCommand { private final CompanionDeviceManagerService mService; private final AssociationStore mAssociationStore; private final CompanionDevicePresenceMonitor mDevicePresenceMonitor; + private final CompanionTransportManager mTransportManager; CompanionDeviceShellCommand(CompanionDeviceManagerService service, AssociationStore associationStore, - CompanionDevicePresenceMonitor devicePresenceMonitor) { + CompanionDevicePresenceMonitor devicePresenceMonitor, + CompanionTransportManager transportManager) { mService = service; mAssociationStore = associationStore; mDevicePresenceMonitor = devicePresenceMonitor; + mTransportManager = transportManager; } @Override @@ -107,6 +111,12 @@ class CompanionDeviceShellCommand extends ShellCommand { } break; + case "create-dummy-transport": + // This command creates a RawTransport in order to test Transport listeners + associationId = getNextIntArgRequired(); + mTransportManager.createDummyTransport(associationId); + break; + default: return handleDefaultCommands(cmd); } @@ -165,5 +175,8 @@ class CompanionDeviceShellCommand extends ShellCommand { pw.println(" for a long time (90 days or as configured via "); pw.println(" \"debug.cdm.cdmservice.cleanup_time_window\" system property). "); pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); + + pw.println(" create-dummy-transport <ASSOCIATION_ID>"); + pw.println(" Create a dummy RawTransport for testing puspose only"); } } diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java index d54aa7c101d7..9677b70f4916 100644 --- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java +++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java @@ -44,6 +44,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.server.LocalServices; import com.android.server.companion.AssociationStore; +import java.io.FileDescriptor; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -350,6 +351,21 @@ public class CompanionTransportManager { this.mSecureTransportEnabled = enabled; } + /** + * For testing purpose only. + * + * Create a dummy RawTransport and notify onTransportChanged listeners. + */ + public void createDummyTransport(int associationId) { + synchronized (mTransports) { + FileDescriptor fd = new FileDescriptor(); + ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd); + Transport transport = new RawTransport(associationId, pfd, mContext); + mTransports.put(associationId, transport); + notifyOnTransportsChanged(); + } + } + private boolean isSecureTransportEnabled() { boolean enabled = !Build.IS_DEBUGGABLE || mSecureTransportEnabled; diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 291c05877c17..96446422dd85 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -151,15 +151,14 @@ public class VirtualDeviceManagerService extends SystemService { } void onCameraAccessBlocked(int appUid) { - synchronized (mVirtualDeviceManagerLock) { - for (int i = 0; i < mVirtualDevices.size(); i++) { - CharSequence deviceName = mVirtualDevices.valueAt(i).getDisplayName(); - mVirtualDevices.valueAt(i).showToastWhereUidIsRunning(appUid, - getContext().getString( - com.android.internal.R.string.vdm_camera_access_denied, - deviceName), - Toast.LENGTH_LONG, Looper.myLooper()); - } + ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot(); + for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { + VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i); + virtualDevice.showToastWhereUidIsRunning(appUid, + getContext().getString( + com.android.internal.R.string.vdm_camera_access_denied, + virtualDevice.getDisplayName()), + Toast.LENGTH_LONG, Looper.myLooper()); } } @@ -265,6 +264,16 @@ public class VirtualDeviceManagerService extends SystemService { cdm.removeOnAssociationsChangedListener(mCdmAssociationListener); } + private ArrayList<VirtualDeviceImpl> getVirtualDevicesSnapshot() { + synchronized (mVirtualDeviceManagerLock) { + ArrayList<VirtualDeviceImpl> virtualDevices = new ArrayList<>(mVirtualDevices.size()); + for (int i = 0; i < mVirtualDevices.size(); i++) { + virtualDevices.add(mVirtualDevices.valueAt(i)); + } + return virtualDevices; + } + } + class VirtualDeviceManagerImpl extends IVirtualDeviceManager.Stub { private final VirtualDeviceImpl.PendingTrampolineCallback mPendingTrampolineCallback = @@ -314,6 +323,17 @@ public class VirtualDeviceManagerService extends SystemService { Objects.requireNonNull(activityListener); Objects.requireNonNull(soundEffectListener); + final UserHandle userHandle = getCallingUserHandle(); + final CameraAccessController cameraAccessController = + getCameraAccessController(userHandle); + final int deviceId = sNextUniqueIndex.getAndIncrement(); + final Consumer<ArraySet<Integer>> runningAppsChangedCallback = + runningUids -> notifyRunningAppsChanged(deviceId, runningUids); + VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(), + associationInfo, VirtualDeviceManagerService.this, token, callingUid, + deviceId, cameraAccessController, + mPendingTrampolineCallback, activityListener, + soundEffectListener, runningAppsChangedCallback, params); synchronized (mVirtualDeviceManagerLock) { if (mVirtualDevices.size() == 0) { final long callindId = Binder.clearCallingIdentity(); @@ -323,21 +343,9 @@ public class VirtualDeviceManagerService extends SystemService { Binder.restoreCallingIdentity(callindId); } } - - final UserHandle userHandle = getCallingUserHandle(); - final CameraAccessController cameraAccessController = - getCameraAccessController(userHandle); - final int deviceId = sNextUniqueIndex.getAndIncrement(); - final Consumer<ArraySet<Integer>> runningAppsChangedCallback = - runningUids -> notifyRunningAppsChanged(deviceId, runningUids); - VirtualDeviceImpl virtualDevice = new VirtualDeviceImpl(getContext(), - associationInfo, VirtualDeviceManagerService.this, token, callingUid, - deviceId, cameraAccessController, - mPendingTrampolineCallback, activityListener, - soundEffectListener, runningAppsChangedCallback, params); mVirtualDevices.put(deviceId, virtualDevice); - return virtualDevice; } + return virtualDevice; } @Override // Binder call @@ -399,12 +407,11 @@ public class VirtualDeviceManagerService extends SystemService { if (displayId == Display.INVALID_DISPLAY || displayId == Display.DEFAULT_DISPLAY) { return Context.DEVICE_ID_DEFAULT; } - synchronized (mVirtualDeviceManagerLock) { - for (int i = 0; i < mVirtualDevices.size(); i++) { - VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i); - if (virtualDevice.isDisplayOwnedByVirtualDevice(displayId)) { - return virtualDevice.getDeviceId(); - } + ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot(); + for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { + VirtualDeviceImpl virtualDevice = virtualDevicesSnapshot.get(i); + if (virtualDevice.isDisplayOwnedByVirtualDevice(displayId)) { + return virtualDevice.getDeviceId(); } } return Context.DEVICE_ID_DEFAULT; @@ -496,10 +503,9 @@ public class VirtualDeviceManagerService extends SystemService { return; } fout.println("Created virtual devices: "); - synchronized (mVirtualDeviceManagerLock) { - for (int i = 0; i < mVirtualDevices.size(); i++) { - mVirtualDevices.valueAt(i).dump(fd, fout, args); - } + ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot(); + for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { + virtualDevicesSnapshot.get(i).dump(fd, fout, args); } } } @@ -516,33 +522,30 @@ public class VirtualDeviceManagerService extends SystemService { @Override public int getDeviceOwnerUid(int deviceId) { + VirtualDeviceImpl virtualDevice; synchronized (mVirtualDeviceManagerLock) { - VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId); - return virtualDevice != null ? virtualDevice.getOwnerUid() : Process.INVALID_UID; + virtualDevice = mVirtualDevices.get(deviceId); } + return virtualDevice != null ? virtualDevice.getOwnerUid() : Process.INVALID_UID; } @Override public @Nullable VirtualSensor getVirtualSensor(int deviceId, int handle) { + VirtualDeviceImpl virtualDevice; synchronized (mVirtualDeviceManagerLock) { - VirtualDeviceImpl virtualDevice = mVirtualDevices.get(deviceId); - if (virtualDevice != null) { - return virtualDevice.getVirtualSensorByHandle(handle); - } + virtualDevice = mVirtualDevices.get(deviceId); } - return null; + return virtualDevice != null ? virtualDevice.getVirtualSensorByHandle(handle) : null; } @Override public @NonNull ArraySet<Integer> getDeviceIdsForUid(int uid) { + ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot(); ArraySet<Integer> result = new ArraySet<>(); - synchronized (mVirtualDeviceManagerLock) { - int size = mVirtualDevices.size(); - for (int i = 0; i < size; i++) { - VirtualDeviceImpl device = mVirtualDevices.valueAt(i); - if (device.isAppRunningOnVirtualDevice(uid)) { - result.add(device.getDeviceId()); - } + for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { + VirtualDeviceImpl device = virtualDevicesSnapshot.get(i); + if (device.isAppRunningOnVirtualDevice(uid)) { + result.add(device.getDeviceId()); } } return result; @@ -630,12 +633,10 @@ public class VirtualDeviceManagerService extends SystemService { @Override public boolean isAppRunningOnAnyVirtualDevice(int uid) { - synchronized (mVirtualDeviceManagerLock) { - int size = mVirtualDevices.size(); - for (int i = 0; i < size; i++) { - if (mVirtualDevices.valueAt(i).isAppRunningOnVirtualDevice(uid)) { - return true; - } + ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot(); + for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { + if (virtualDevicesSnapshot.get(i).isAppRunningOnVirtualDevice(uid)) { + return true; } } return false; @@ -643,12 +644,10 @@ public class VirtualDeviceManagerService extends SystemService { @Override public boolean isDisplayOwnedByAnyVirtualDevice(int displayId) { - synchronized (mVirtualDeviceManagerLock) { - int size = mVirtualDevices.size(); - for (int i = 0; i < size; i++) { - if (mVirtualDevices.valueAt(i).isDisplayOwnedByVirtualDevice(displayId)) { - return true; - } + ArrayList<VirtualDeviceImpl> virtualDevicesSnapshot = getVirtualDevicesSnapshot(); + for (int i = 0; i < virtualDevicesSnapshot.size(); i++) { + if (virtualDevicesSnapshot.get(i).isDisplayOwnedByVirtualDevice(displayId)) { + return true; } } return false; diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java index c6f63dd73a25..12ee13183221 100644 --- a/services/core/java/android/os/BatteryStatsInternal.java +++ b/services/core/java/android/os/BatteryStatsInternal.java @@ -39,12 +39,14 @@ public abstract class BatteryStatsInternal { public static final int CPU_WAKEUP_SUBSYSTEM_UNKNOWN = -1; public static final int CPU_WAKEUP_SUBSYSTEM_ALARM = 1; public static final int CPU_WAKEUP_SUBSYSTEM_WIFI = 2; + public static final int CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER = 3; /** @hide */ @IntDef(prefix = {"CPU_WAKEUP_SUBSYSTEM_"}, value = { CPU_WAKEUP_SUBSYSTEM_UNKNOWN, CPU_WAKEUP_SUBSYSTEM_ALARM, CPU_WAKEUP_SUBSYSTEM_WIFI, + CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, }) @Retention(RetentionPolicy.SOURCE) @interface CpuWakeupSubsystem { diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java index 3487613d313c..5156c541011f 100644 --- a/services/core/java/com/android/server/DockObserver.java +++ b/services/core/java/com/android/server/DockObserver.java @@ -70,7 +70,7 @@ final class DockObserver extends SystemService { private boolean mUpdatesStopped; - private final boolean mKeepDreamingWhenUndocking; + private final boolean mKeepDreamingWhenUnplugging; private final boolean mAllowTheaterModeWakeFromDock; private final List<ExtconStateConfig> mExtconStateConfigs; @@ -167,8 +167,8 @@ final class DockObserver extends SystemService { mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mAllowTheaterModeWakeFromDock = context.getResources().getBoolean( com.android.internal.R.bool.config_allowTheaterModeWakeFromDock); - mKeepDreamingWhenUndocking = context.getResources().getBoolean( - com.android.internal.R.bool.config_keepDreamingWhenUndocking); + mKeepDreamingWhenUnplugging = context.getResources().getBoolean( + com.android.internal.R.bool.config_keepDreamingWhenUnplugging); mDeviceProvisionedObserver = new DeviceProvisionedObserver(mHandler); mExtconStateConfigs = loadExtconStateConfigs(context); @@ -237,7 +237,7 @@ final class DockObserver extends SystemService { } private boolean allowWakeFromDock() { - if (mKeepDreamingWhenUndocking) { + if (mKeepDreamingWhenUnplugging) { return false; } return (mAllowTheaterModeWakeFromDock diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS index 409f0541eed7..123cd3288343 100644 --- a/services/core/java/com/android/server/OWNERS +++ b/services/core/java/com/android/server/OWNERS @@ -27,6 +27,7 @@ per-file **IpSec* = file:/services/core/java/com/android/server/net/OWNERS per-file **IpSec* = file:/services/core/java/com/android/server/vcn/OWNERS per-file *Location* = file:/services/core/java/com/android/server/location/OWNERS per-file *Network* = file:/services/core/java/com/android/server/net/OWNERS +per-file *SoundTrigger* = file:/media/java/android/media/soundtrigger/OWNERS per-file *Storage* = file:/core/java/android/os/storage/OWNERS per-file *TimeUpdate* = file:/services/core/java/com/android/server/timezonedetector/OWNERS per-file DynamicSystemService.java = file:/packages/DynamicSystemInstallationService/OWNERS diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index e248007eca19..ee18ed566725 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1019,6 +1019,24 @@ public final class ActiveServices { r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), service, neededGrants, callingUid, callingProcessName, callingPackage)); + // We want to allow scheduling user-initiated jobs when the app is running a + // foreground service that was started in the same conditions that allows for scheduling + // UI jobs. More explicitly, we want to allow scheduling UI jobs when the app is running + // an FGS that started when the app was in the TOP or a BAL-approved state. + final boolean isFgs = r.isForeground || r.fgRequired; + if (isFgs) { + // As of Android UDC, the conditions required for the while-in-use permissions + // are the same conditions that we want, so we piggyback on that logic. + // Use that as a shortcut if possible to avoid having to recheck all the conditions. + final boolean whileInUseAllowsUiJobScheduling = + ActivityManagerService.doesReasonCodeAllowSchedulingUserInitiatedJobs( + r.mAllowWhileInUsePermissionInFgsReason); + r.updateAllowUiJobScheduling(whileInUseAllowsUiJobScheduling + || mAm.canScheduleUserInitiatedJobs(callingUid, callingPid, callingPackage)); + } else { + r.updateAllowUiJobScheduling(false); + } + if (fgRequired) { // We are now effectively running a foreground service. synchronized (mAm.mProcessStats.mLock) { @@ -7362,26 +7380,12 @@ public final class ActiveServices { } else { allowWhileInUse = REASON_UNKNOWN; } - // We want to allow scheduling user-initiated jobs when the app is running a - // foreground service that was started in the same conditions that allows for scheduling - // UI jobs. More explicitly, we want to allow scheduling UI jobs when the app is running - // an FGS that started when the app was in the TOP or a BAL-approved state. - // As of Android UDC, the conditions required for the while-in-use permissions - // are the same conditions that we want, so we piggyback on that logic. - // We use that as a shortcut if possible so we don't have to recheck all the conditions. - final boolean isFgs = r.isForeground || r.fgRequired; - if (isFgs) { - r.updateAllowUiJobScheduling(ActivityManagerService - .doesReasonCodeAllowSchedulingUserInitiatedJobs(allowWhileInUse) - || mAm.canScheduleUserInitiatedJobs( - callingUid, callingPid, callingPackage, true)); - } else { - r.updateAllowUiJobScheduling(false); - } + r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse; } void resetFgsRestrictionLocked(ServiceRecord r) { r.mAllowWhileInUsePermissionInFgs = false; + r.mAllowWhileInUsePermissionInFgsReason = REASON_DENIED; r.mAllowStartForeground = REASON_DENIED; r.mInfoAllowStartForeground = null; r.mInfoTempFgsAllowListReason = null; @@ -7425,14 +7429,17 @@ public final class ActiveServices { final int uidState = mAm.getUidStateLocked(callingUid); if (ret == REASON_DENIED) { - // Is the calling UID at PROCESS_STATE_TOP or above? + // Allow FGS while-in-use if the caller's process state is PROCESS_STATE_PERSISTENT, + // PROCESS_STATE_PERSISTENT_UI or PROCESS_STATE_TOP. if (uidState <= PROCESS_STATE_TOP) { ret = getReasonCodeFromProcState(uidState); } } if (ret == REASON_DENIED) { - // Does the calling UID have any visible activity? + // Allow FGS while-in-use if the caller has visible activity. + // Here we directly check ActivityTaskManagerService, instead of checking + // PendingStartActivityUids in ActivityManagerService, which gives the same result. final boolean isCallingUidVisible = mAm.mAtmInternal.isUidForeground(callingUid); if (isCallingUidVisible) { ret = REASON_UID_VISIBLE; @@ -7440,7 +7447,8 @@ public final class ActiveServices { } if (ret == REASON_DENIED) { - // Is the allow activity background start flag on? + // Allow FGS while-in-use if the background activity start flag is on. Because + // activity start can lead to FGS start in TOP state and obtain while-in-use. if (backgroundStartPrivileges.allowsBackgroundActivityStarts()) { ret = REASON_START_ACTIVITY_FLAG; } @@ -7449,6 +7457,7 @@ public final class ActiveServices { if (ret == REASON_DENIED) { boolean isCallerSystem = false; final int callingAppId = UserHandle.getAppId(callingUid); + // Allow FGS while-in-use for a list of special UIDs. switch (callingAppId) { case ROOT_UID: case SYSTEM_UID: @@ -7467,6 +7476,10 @@ public final class ActiveServices { } if (ret == REASON_DENIED) { + // Allow FGS while-in-use if the WindowManager allows background activity start. + // This is mainly to get the 10 seconds grace period if any activity in the caller has + // either started or finished very recently. The binding flag + // BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS is also allowed by the check here. final Integer allowedType = mAm.mProcessList.searchEachLruProcessesLOSP(false, pr -> { if (pr.uid == callingUid) { if (pr.getWindowProcessController().areBackgroundFgsStartsAllowed()) { @@ -7481,6 +7494,12 @@ public final class ActiveServices { } if (ret == REASON_DENIED) { + // Allow FGS while-in-use if the caller UID is in ActivityManagerService's + // mFgsWhileInUseTempAllowList. This is a temp allowlist to allow FGS while-in-use. It + // is used when MediaSessionService's bluetooth button or play/resume/stop commands are + // issued. The typical temp allowlist duration is 10 seconds. + // This temp allowlist mechanism can also be called by other system_server internal + // components such as Telephone/VOIP if they want to start a FGS and get while-in-use. if (mAm.mInternal.isTempAllowlistedForFgsWhileInUse(callingUid)) { return REASON_TEMP_ALLOWED_WHILE_IN_USE; } @@ -7488,6 +7507,8 @@ public final class ActiveServices { if (ret == REASON_DENIED) { if (targetProcess != null) { + // Allow FGS while-in-use if the caller of the instrumentation has + // START_ACTIVITIES_FROM_BACKGROUND permission. ActiveInstrumentation instr = targetProcess.getActiveInstrumentation(); if (instr != null && instr.mHasBackgroundActivityStartsPermission) { ret = REASON_INSTR_BACKGROUND_ACTIVITY_PERMISSION; @@ -7496,6 +7517,9 @@ public final class ActiveServices { } if (ret == REASON_DENIED) { + // Allow FGS while-in-use if the caller has START_ACTIVITIES_FROM_BACKGROUND + // permission, because starting an activity can lead to starting FGS from the TOP state + // and obtain while-in-use. if (mAm.checkPermission(START_ACTIVITIES_FROM_BACKGROUND, callingPid, callingUid) == PERMISSION_GRANTED) { ret = REASON_BACKGROUND_ACTIVITY_PERMISSION; @@ -7503,6 +7527,8 @@ public final class ActiveServices { } if (ret == REASON_DENIED) { + // Allow FGS while-in-use if the caller is in the while-in-use allowlist. Right now + // AttentionService and SystemCaptionsService packageName are in this allowlist. if (verifyPackage(callingPackage, callingUid)) { final boolean isAllowedPackage = mAllowListWhileInUsePermissionInFgs.contains(callingPackage); @@ -7517,7 +7543,7 @@ public final class ActiveServices { } if (ret == REASON_DENIED) { - // Is the calling UID a device owner app? + // Allow FGS while-in-use if the caller is the device owner. final boolean isDeviceOwner = mAm.mInternal.isDeviceOwner(callingUid); if (isDeviceOwner) { ret = REASON_DEVICE_OWNER; diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index ae5dbe11495a..44e198b53761 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -213,7 +213,7 @@ final class ActivityManagerConstants extends ContentObserver { private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false; private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000; - private static final boolean DEFAULT_USE_MODERN_TRIM = false; + private static final boolean DEFAULT_USE_MODERN_TRIM = true; /** * Same as {@link TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_NOT_ALLOWED} diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index b32f8c973e6a..97d34b8dd718 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -6189,8 +6189,8 @@ public class ActivityManagerService extends IActivityManager.Stub final ProcessServiceRecord psr = pr.mServices; if (psr != null && psr.hasForegroundServices()) { - for (int s = psr.numberOfExecutingServices() - 1; s >= 0; --s) { - final ServiceRecord sr = psr.getExecutingServiceAt(s); + for (int s = psr.numberOfRunningServices() - 1; s >= 0; --s) { + final ServiceRecord sr = psr.getRunningServiceAt(s); if (sr.isForeground && sr.mAllowUiJobScheduling) { return true; } @@ -6205,12 +6205,7 @@ public class ActivityManagerService extends IActivityManager.Stub * {@link android.app.job.JobInfo.Builder#setUserInitiated(boolean) user-initiated job}. */ // TODO(262260570): log allow reason to an atom - private boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) { - return canScheduleUserInitiatedJobs(uid, pid, pkgName, false); - } - - boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName, - boolean skipWhileInUseCheck) { + boolean canScheduleUserInitiatedJobs(int uid, int pid, String pkgName) { synchronized (this) { final ProcessRecord processRecord; synchronized (mPidsSelfLocked) { @@ -6240,7 +6235,7 @@ public class ActivityManagerService extends IActivityManager.Stub // As of Android UDC, the conditions required to grant a while-in-use permission // covers the majority of those cases, and so we piggyback on that logic as the base. // Missing cases are added after. - if (!skipWhileInUseCheck && mServices.canAllowWhileInUsePermissionInFgsLocked( + if (mServices.canAllowWhileInUsePermissionInFgsLocked( pid, uid, pkgName, processRecord, backgroundStartPrivileges)) { return true; } diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index e080a80e1c37..17a0d62c27b3 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -247,6 +247,10 @@ final class ActivityManagerShellCommand extends ShellCommand { return runSendBroadcast(pw); case "compact": return runCompact(pw); + case "freeze": + return runFreeze(pw); + case "unfreeze": + return runUnfreeze(pw); case "instrument": getOutPrintWriter().println("Error: must be invoked through 'am instrument'."); return -1; @@ -1074,20 +1078,10 @@ final class ActivityManagerShellCommand extends ShellCommand { boolean isFullCompact = op.equals("full"); boolean isSomeCompact = op.equals("some"); if (isFullCompact || isSomeCompact) { - String processName = getNextArgRequired(); - synchronized (mInternal.mProcLock) { - // Default to current user - int userId = mInterface.getCurrentUserId(); - String userOpt = getNextOption(); - if (userOpt != null && "--user".equals(userOpt)) { - int inputUserId = UserHandle.parseUserArg(getNextArgRequired()); - if (inputUserId != UserHandle.USER_CURRENT) { - userId = inputUserId; - } - } - final int uid = - mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId); - app = mInternal.getProcessRecordLocked(processName, uid); + app = getProcessFromShell(); + if (app == null) { + getErrPrintWriter().println("Error: could not find process"); + return -1; } pw.println("Process record found pid: " + app.mPid); if (isFullCompact) { @@ -1143,6 +1137,93 @@ final class ActivityManagerShellCommand extends ShellCommand { return 0; } + @NeverCompile + int runFreeze(PrintWriter pw) throws RemoteException { + String freezerOpt = getNextOption(); + boolean isSticky = false; + if (freezerOpt != null) { + isSticky = freezerOpt.equals("--sticky"); + } + ProcessRecord app = getProcessFromShell(); + if (app == null) { + getErrPrintWriter().println("Error: could not find process"); + return -1; + } + pw.println("Freezing pid: " + app.mPid + " sticky=" + isSticky); + synchronized (mInternal) { + synchronized (mInternal.mProcLock) { + app.mOptRecord.setFreezeSticky(isSticky); + mInternal.mOomAdjuster.mCachedAppOptimizer.freezeAppAsyncInternalLSP(app, 0, true); + } + } + return 0; + } + + @NeverCompile + int runUnfreeze(PrintWriter pw) throws RemoteException { + String freezerOpt = getNextOption(); + boolean isSticky = false; + if (freezerOpt != null) { + isSticky = freezerOpt.equals("--sticky"); + } + ProcessRecord app = getProcessFromShell(); + if (app == null) { + getErrPrintWriter().println("Error: could not find process"); + return -1; + } + pw.println("Unfreezing pid: " + app.mPid); + synchronized (mInternal) { + synchronized (mInternal.mProcLock) { + synchronized (mInternal.mOomAdjuster.mCachedAppOptimizer.mFreezerLock) { + app.mOptRecord.setFreezeSticky(isSticky); + mInternal.mOomAdjuster.mCachedAppOptimizer.unfreezeAppInternalLSP(app, 0, + false); + } + } + } + return 0; + } + + /** + * Parses from the shell the process name and user id if provided and provides the corresponding + * {@link ProcessRecord)} If no user is provided, it will fallback to current user. + * Example usage: {@code <processname> --user current} or {@code <processname>} + * @return process record of process, null if none found. + * @throws RemoteException + */ + @NeverCompile + ProcessRecord getProcessFromShell() throws RemoteException { + ProcessRecord app; + String processName = getNextArgRequired(); + synchronized (mInternal.mProcLock) { + // Default to current user + int userId = getUserIdFromShellOrFallback(); + final int uid = + mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId); + app = mInternal.getProcessRecordLocked(processName, uid); + } + return app; + } + + /** + * @return User id from command line provided in the form of + * {@code --user <userid|current|all>} and if the argument is not found it will fallback + * to current user. + * @throws RemoteException + */ + @NeverCompile + int getUserIdFromShellOrFallback() throws RemoteException { + int userId = mInterface.getCurrentUserId(); + String userOpt = getNextOption(); + if (userOpt != null && "--user".equals(userOpt)) { + int inputUserId = UserHandle.parseUserArg(getNextArgRequired()); + if (inputUserId != UserHandle.USER_CURRENT) { + userId = inputUserId; + } + } + return userId; + } + int runDumpHeap(PrintWriter pw) throws RemoteException { final PrintWriter err = getErrPrintWriter(); boolean managed = true; @@ -4061,6 +4142,14 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" Perform a native compaction for process with <pid>."); pw.println(" some: execute file compaction."); pw.println(" full: execute anon + file compaction."); + pw.println(" freeze [--sticky] <processname> [--user <USER_ID>]"); + pw.println(" Freeze a process."); + pw.println(" --sticky: persists the frozen state for the process lifetime or"); + pw.println(" until an unfreeze is triggered via shell"); + pw.println(" unfreeze [--sticky] <processname> [--user <USER_ID>]"); + pw.println(" Unfreeze a process."); + pw.println(" --sticky: persists the unfrozen state for the process lifetime or"); + pw.println(" until a freeze is triggered via shell"); pw.println(" instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]"); pw.println(" [--user <USER_ID> | current]"); pw.println(" [--no-hidden-api-checks [--no-test-api-access]]"); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index ed297d0867a1..0744f751354d 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -108,8 +108,8 @@ import com.android.server.power.stats.BatteryExternalStatsWorker; import com.android.server.power.stats.BatteryStatsImpl; import com.android.server.power.stats.BatteryUsageStatsProvider; import com.android.server.power.stats.BatteryUsageStatsStore; -import com.android.server.power.stats.CpuWakeupStats; import com.android.server.power.stats.SystemServerCpuThreadReader.SystemServiceCpuThreadTimes; +import com.android.server.power.stats.wakeups.CpuWakeupStats; import java.io.File; import java.io.FileDescriptor; @@ -515,13 +515,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub @Override public void noteCpuWakingActivity(int subsystem, long elapsedMillis, int... uids) { Objects.requireNonNull(uids); - mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids); + mHandler.post(() -> mCpuWakeupStats.noteWakingActivity(subsystem, elapsedMillis, uids)); } - @Override public void noteWakingSoundTrigger(long elapsedMillis, int uid) { - // TODO(b/267717665): Pipe to noteCpuWakingActivity once SoundTrigger starts using this. - Slog.w(TAG, "Sound trigger event dispatched to uid " + uid); + noteCpuWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, elapsedMillis, uid); } } diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index 9e61ce405ca7..0767218ec065 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -20,6 +20,7 @@ import static com.android.internal.util.Preconditions.checkState; import static com.android.server.am.BroadcastRecord.deliveryStateToString; import static com.android.server.am.BroadcastRecord.isReceiverEquals; +import android.annotation.CheckResult; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -187,6 +188,12 @@ class BroadcastProcessQueue { private @Reason int mRunnableAtReason = REASON_EMPTY; private boolean mRunnableAtInvalidated; + /** + * Last state applied by {@link #updateDeferredStates}, used to quickly + * determine if a state transition is occurring. + */ + private boolean mLastDeferredStates; + private boolean mUidCached; private boolean mProcessInstrumented; private boolean mProcessPersistent; @@ -235,7 +242,15 @@ class BroadcastProcessQueue { */ @Nullable public BroadcastRecord enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, - int recordIndex, boolean wouldBeSkipped) { + int recordIndex, boolean wouldBeSkipped, + @NonNull BroadcastConsumer deferredStatesApplyConsumer) { + // When updateDeferredStates() has already applied a deferred state to + // all pending items, apply to this new broadcast too + if (mLastDeferredStates && record.deferUntilActive + && (record.getDeliveryState(recordIndex) == BroadcastRecord.DELIVERY_PENDING)) { + deferredStatesApplyConsumer.accept(record, recordIndex); + } + if (record.isReplacePending()) { final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex, wouldBeSkipped); @@ -340,7 +355,12 @@ class BroadcastProcessQueue { * Predicates that choose to remove a broadcast <em>must</em> finish * delivery of the matched broadcast, to ensure that situations like ordered * broadcasts are handled consistently. + * + * @return if this operation may have changed internal state, indicating + * that the caller is responsible for invoking + * {@link BroadcastQueueModernImpl#updateRunnableList} */ + @CheckResult public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate, @NonNull BroadcastConsumer consumer, boolean andRemove) { boolean didSomething = false; @@ -353,6 +373,7 @@ class BroadcastProcessQueue { return didSomething; } + @CheckResult private boolean forEachMatchingBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue, @NonNull BroadcastPredicate predicate, @NonNull BroadcastConsumer consumer, boolean andRemove) { @@ -369,6 +390,10 @@ class BroadcastProcessQueue { args.recycle(); it.remove(); onBroadcastDequeued(record, recordIndex, recordWouldBeSkipped); + } else { + // Even if we're leaving broadcast in queue, it may have + // been mutated in such a way to change our runnable time + invalidateRunnableAt(); } didSomething = true; } @@ -380,32 +405,44 @@ class BroadcastProcessQueue { /** * Update the actively running "warm" process for this process. + * + * @return if this operation may have changed internal state, indicating + * that the caller is responsible for invoking + * {@link BroadcastQueueModernImpl#updateRunnableList} */ - public void setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) { + @CheckResult + public boolean setProcessAndUidCached(@Nullable ProcessRecord app, boolean uidCached) { this.app = app; - if (app != null) { - setUidCached(uidCached); - setProcessInstrumented(app.getActiveInstrumentation() != null); - setProcessPersistent(app.isPersistent()); - } else { - setUidCached(uidCached); - setProcessInstrumented(false); - setProcessPersistent(false); - } // Since we may have just changed our PID, invalidate cached strings mCachedToString = null; mCachedToShortString = null; + + boolean didSomething = false; + if (app != null) { + didSomething |= setUidCached(uidCached); + didSomething |= setProcessInstrumented(app.getActiveInstrumentation() != null); + didSomething |= setProcessPersistent(app.isPersistent()); + } else { + didSomething |= setUidCached(uidCached); + didSomething |= setProcessInstrumented(false); + didSomething |= setProcessPersistent(false); + } + return didSomething; } /** * Update if this process is in the "cached" state, typically signaling that * broadcast dispatch should be paused or delayed. */ - private void setUidCached(boolean uidCached) { + @CheckResult + private boolean setUidCached(boolean uidCached) { if (mUidCached != uidCached) { mUidCached = uidCached; invalidateRunnableAt(); + return true; + } else { + return false; } } @@ -414,10 +451,14 @@ class BroadcastProcessQueue { * signaling that broadcast dispatch should bypass all pauses or delays, to * avoid holding up test suites. */ - private void setProcessInstrumented(boolean instrumented) { + @CheckResult + private boolean setProcessInstrumented(boolean instrumented) { if (mProcessInstrumented != instrumented) { mProcessInstrumented = instrumented; invalidateRunnableAt(); + return true; + } else { + return false; } } @@ -425,10 +466,14 @@ class BroadcastProcessQueue { * Update if this process is in the "persistent" state, which signals broadcast dispatch should * bypass all pauses or delays to prevent the system from becoming out of sync with itself. */ - private void setProcessPersistent(boolean persistent) { + @CheckResult + private boolean setProcessPersistent(boolean persistent) { if (mProcessPersistent != persistent) { mProcessPersistent = persistent; invalidateRunnableAt(); + return true; + } else { + return false; } } @@ -648,8 +693,20 @@ class BroadcastProcessQueue { return mActive != null; } - void forceDelayBroadcastDelivery(long delayedDurationMs) { - mForcedDelayedDurationMs = delayedDurationMs; + /** + * @return if this operation may have changed internal state, indicating + * that the caller is responsible for invoking + * {@link BroadcastQueueModernImpl#updateRunnableList} + */ + @CheckResult + boolean forceDelayBroadcastDelivery(long delayedDurationMs) { + if (mForcedDelayedDurationMs != delayedDurationMs) { + mForcedDelayedDurationMs = delayedDurationMs; + invalidateRunnableAt(); + return true; + } else { + return false; + } } /** @@ -721,10 +778,21 @@ class BroadcastProcessQueue { * broadcasts would be prioritized for dispatching, even if there are urgent broadcasts * waiting. This is typically used in case there are callers waiting for "barrier" to be * reached. + * + * @return if this operation may have changed internal state, indicating + * that the caller is responsible for invoking + * {@link BroadcastQueueModernImpl#updateRunnableList} */ + @CheckResult @VisibleForTesting - void setPrioritizeEarliest(boolean prioritizeEarliest) { - mPrioritizeEarliest = prioritizeEarliest; + boolean setPrioritizeEarliest(boolean prioritizeEarliest) { + if (mPrioritizeEarliest != prioritizeEarliest) { + mPrioritizeEarliest = prioritizeEarliest; + invalidateRunnableAt(); + return true; + } else { + return false; + } } /** @@ -912,9 +980,9 @@ class BroadcastProcessQueue { } /** - * Update {@link #getRunnableAt()} if it's currently invalidated. + * Update {@link #getRunnableAt()}, when needed. */ - private void updateRunnableAt() { + void updateRunnableAt() { if (!mRunnableAtInvalidated) return; mRunnableAtInvalidated = false; @@ -1027,10 +1095,44 @@ class BroadcastProcessQueue { } /** + * Update {@link BroadcastRecord.DELIVERY_DEFERRED} states of all our + * pending broadcasts, when needed. + */ + void updateDeferredStates(@NonNull BroadcastConsumer applyConsumer, + @NonNull BroadcastConsumer clearConsumer) { + // When all we have pending is deferred broadcasts, and we're cached, + // then we want everything to be marked deferred + final boolean wantDeferredStates = (mCountDeferred > 0) + && (mCountDeferred == mCountEnqueued) && mUidCached; + + if (mLastDeferredStates != wantDeferredStates) { + mLastDeferredStates = wantDeferredStates; + if (wantDeferredStates) { + forEachMatchingBroadcast((r, i) -> { + return r.deferUntilActive + && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_PENDING); + }, applyConsumer, false); + } else { + forEachMatchingBroadcast((r, i) -> { + return r.deferUntilActive + && (r.getDeliveryState(i) == BroadcastRecord.DELIVERY_DEFERRED); + }, clearConsumer, false); + } + } + } + + /** * Check overall health, confirming things are in a reasonable state and * that we're not wedged. */ public void assertHealthLocked() { + // If we're not actively running, we should be sorted into the runnable + // list, and if we're invalidated then someone likely forgot to invoke + // updateRunnableList() to re-sort us into place + if (!isActive()) { + checkState(!mRunnableAtInvalidated, "mRunnableAtInvalidated"); + } + assertHealthLocked(mPending); assertHealthLocked(mPendingUrgent); assertHealthLocked(mPendingOffload); @@ -1133,19 +1235,30 @@ class BroadcastProcessQueue { return mCachedToShortString; } + public String describeStateLocked() { + return describeStateLocked(SystemClock.uptimeMillis()); + } + + public String describeStateLocked(@UptimeMillisLong long now) { + final StringBuilder sb = new StringBuilder(); + if (isRunnable()) { + sb.append("runnable at "); + TimeUtils.formatDuration(getRunnableAt(), now, sb); + } else { + sb.append("not runnable"); + } + sb.append(" because "); + sb.append(reasonToString(mRunnableAtReason)); + return sb.toString(); + } + @NeverCompile public void dumpLocked(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw) { if ((mActive == null) && isEmpty()) return; pw.print(toShortString()); - if (isRunnable()) { - pw.print(" runnable at "); - TimeUtils.formatDuration(getRunnableAt(), now, pw); - } else { - pw.print(" not runnable"); - } - pw.print(" because "); - pw.print(reasonToString(mRunnableAtReason)); + pw.print(" "); + pw.print(describeStateLocked(now)); pw.println(); pw.increaseIndent(); diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index e532c15addd0..8735f8a37b8b 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -327,6 +327,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue { return; } + // To place ourselves correctly in the runnable list, we may need to + // update internals that may have been invalidated; we wait until now at + // the last possible moment to avoid duplicated work + queue.updateDeferredStates(mBroadcastConsumerDeferApply, mBroadcastConsumerDeferClear); + queue.updateRunnableAt(); + final boolean wantQueue = queue.isRunnable(); final boolean inQueue = (queue == mRunnableHead) || (queue.runnableAtPrev != null) || (queue.runnableAtNext != null); @@ -352,8 +358,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // If app isn't running, and there's nothing in the queue, clean up if (queue.isEmpty() && !queue.isActive() && !queue.isProcessWarm()) { removeProcessQueue(queue.processName, queue.uid); - } else { - updateQueueDeferred(queue); } } @@ -619,14 +623,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } enqueuedBroadcast = true; final BroadcastRecord replacedBroadcast = queue.enqueueOrReplaceBroadcast( - r, i, wouldBeSkipped); + r, i, wouldBeSkipped, mBroadcastConsumerDeferApply); if (replacedBroadcast != null) { replacedBroadcasts.add(replacedBroadcast); } - if (r.isDeferUntilActive() && queue.isDeferredUntilActive()) { - setDeliveryState(queue, null, r, i, receiver, BroadcastRecord.DELIVERY_DEFERRED, - "deferred at enqueue time"); - } updateRunnableList(queue); enqueueUpdateRunningList(); } @@ -1249,14 +1249,14 @@ class BroadcastQueueModernImpl extends BroadcastQueue { r.resultExtras = null; }; - private final BroadcastConsumer mBroadcastConsumerDefer = (r, i) -> { + private final BroadcastConsumer mBroadcastConsumerDeferApply = (r, i) -> { setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_DEFERRED, - "mBroadcastConsumerDefer"); + "mBroadcastConsumerDeferApply"); }; - private final BroadcastConsumer mBroadcastConsumerUndoDefer = (r, i) -> { + private final BroadcastConsumer mBroadcastConsumerDeferClear = (r, i) -> { setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_PENDING, - "mBroadcastConsumerUndoDefer"); + "mBroadcastConsumerDeferClear"); }; /** @@ -1272,7 +1272,8 @@ class BroadcastQueueModernImpl extends BroadcastQueue { final long now = SystemClock.uptimeMillis(); if (now > mLastTestFailureTime + DateUtils.SECOND_IN_MILLIS) { mLastTestFailureTime = now; - pw.println("Test " + label + " failed due to " + leaf.toShortString()); + pw.println("Test " + label + " failed due to " + leaf.toShortString() + " " + + leaf.describeStateLocked()); pw.flush(); } return false; @@ -1309,34 +1310,25 @@ class BroadcastQueueModernImpl extends BroadcastQueue { return didSomething; } - private void forEachMatchingQueue( + private boolean forEachMatchingQueue( @NonNull Predicate<BroadcastProcessQueue> queuePredicate, @NonNull Consumer<BroadcastProcessQueue> queueConsumer) { + boolean didSomething = false; for (int i = mProcessQueues.size() - 1; i >= 0; i--) { BroadcastProcessQueue leaf = mProcessQueues.valueAt(i); while (leaf != null) { if (queuePredicate.test(leaf)) { queueConsumer.accept(leaf); updateRunnableList(leaf); + didSomething = true; } leaf = leaf.processNameNext; } } - } - - private void updateQueueDeferred( - @NonNull BroadcastProcessQueue leaf) { - if (leaf.isDeferredUntilActive()) { - leaf.forEachMatchingBroadcast((r, i) -> { - return r.deferUntilActive && (r.getDeliveryState(i) - == BroadcastRecord.DELIVERY_PENDING); - }, mBroadcastConsumerDefer, false); - } else if (leaf.hasDeferredBroadcasts()) { - leaf.forEachMatchingBroadcast((r, i) -> { - return r.deferUntilActive && (r.getDeliveryState(i) - == BroadcastRecord.DELIVERY_DEFERRED); - }, mBroadcastConsumerUndoDefer, false); + if (didSomething) { + enqueueUpdateRunningList(); } + return didSomething; } @Override @@ -1359,8 +1351,6 @@ class BroadcastQueueModernImpl extends BroadcastQueue { // Update internal state by refreshing values previously // read from any known running process setQueueProcess(leaf, leaf.app); - updateQueueDeferred(leaf); - updateRunnableList(leaf); leaf = leaf.processNameNext; } enqueueUpdateRunningList(); @@ -1529,19 +1519,31 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } } + @SuppressWarnings("CheckResult") private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) { if (!queue.isProcessWarm()) { - setQueueProcess(queue, mService.getProcessRecordLocked(queue.processName, queue.uid)); + // This is a bit awkward; we're in the middle of traversing the + // runnable queue, so we can't reorder that list if the runnable + // time changes here. However, if this process was just found to be + // warm via this operation, we're going to immediately promote it to + // be running, and any side effect of this operation will then apply + // after it's finished and is returned to the runnable list. + queue.setProcessAndUidCached( + mService.getProcessRecordLocked(queue.processName, queue.uid), + mUidCached.get(queue.uid, false)); } } /** * Update the {@link ProcessRecord} associated with the given - * {@link BroadcastProcessQueue}. + * {@link BroadcastProcessQueue}. Also updates any runnable status that + * might have changed as a side-effect. */ private void setQueueProcess(@NonNull BroadcastProcessQueue queue, @Nullable ProcessRecord app) { - queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false)); + if (queue.setProcessAndUidCached(app, mUidCached.get(queue.uid, false))) { + updateRunnableList(queue); + } } /** diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index f42087ff8006..1426cfd65286 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -342,7 +342,7 @@ public final class CachedAppOptimizer { private final ActivityManagerGlobalLock mProcLock; - private final Object mFreezerLock = new Object(); + public final Object mFreezerLock = new Object(); private final OnPropertiesChangedListener mOnFlagsChangedListener = new OnPropertiesChangedListener() { @@ -763,8 +763,9 @@ public final class CachedAppOptimizer { pw.println(" Apps frozen: " + size); for (int i = 0; i < size; i++) { ProcessRecord app = mFrozenProcesses.valueAt(i); - pw.println(" " + app.mOptRecord.getFreezeUnfreezeTime() - + ": " + app.getPid() + " " + app.processName); + pw.println(" " + app.mOptRecord.getFreezeUnfreezeTime() + ": " + app.getPid() + + " " + app.processName + + (app.mOptRecord.isFreezeSticky() ? " (sticky)" : "")); } if (!mPendingCompactionProcesses.isEmpty()) { @@ -1283,12 +1284,26 @@ public final class CachedAppOptimizer { @GuardedBy({"mAm", "mProcLock"}) void freezeAppAsyncLSP(ProcessRecord app) { + freezeAppAsyncInternalLSP(app, mFreezerDebounceTimeout, false); + } + + @GuardedBy({"mAm", "mProcLock"}) + void freezeAppAsyncInternalLSP(ProcessRecord app, long delayMillis, boolean force) { final ProcessCachedOptimizerRecord opt = app.mOptRecord; if (opt.isPendingFreeze()) { // Skip redundant DO_FREEZE message return; } + if (opt.isFreezeSticky() && !force) { + if (DEBUG_FREEZER) { + Slog.d(TAG_AM, + "Skip freezing because unfrozen state is sticky pid=" + app.getPid() + " " + + app.processName); + } + return; + } + if (mAm.mConstants.USE_MODERN_TRIM && app.mState.getSetAdj() >= ProcessList.CACHED_APP_MIN_ADJ) { final IApplicationThread thread = app.getThread(); @@ -1301,9 +1316,8 @@ public final class CachedAppOptimizer { } } mFreezeHandler.sendMessageDelayed( - mFreezeHandler.obtainMessage( - SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app), - mFreezerDebounceTimeout); + mFreezeHandler.obtainMessage(SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app), + delayMillis); opt.setPendingFreeze(true); if (DEBUG_FREEZER) { Slog.d(TAG_AM, "Async freezing " + app.getPid() + " " + app.processName); @@ -1311,9 +1325,19 @@ public final class CachedAppOptimizer { } @GuardedBy({"mAm", "mProcLock", "mFreezerLock"}) - void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason) { + void unfreezeAppInternalLSP(ProcessRecord app, @UnfreezeReason int reason, boolean force) { final int pid = app.getPid(); final ProcessCachedOptimizerRecord opt = app.mOptRecord; + boolean sticky = opt.isFreezeSticky(); + if (sticky && !force) { + // Sticky freezes will not change their state unless forced out of it. + if (DEBUG_FREEZER) { + Slog.d(TAG_AM, + "Skip unfreezing because frozen state is sticky pid=" + pid + " " + + app.processName); + } + return; + } if (opt.isPendingFreeze()) { // Remove pending DO_FREEZE message mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app); @@ -1326,8 +1350,7 @@ public final class CachedAppOptimizer { UidRecord uidRec = app.getUidRecord(); if (uidRec != null && uidRec.isFrozen()) { uidRec.setFrozen(false); - mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app); - reportOneUidFrozenStateChanged(app.uid, false); + postUidFrozenMessage(uidRec.getUid(), false); } opt.setFreezerOverride(false); @@ -1407,7 +1430,7 @@ public final class CachedAppOptimizer { @GuardedBy({"mAm", "mProcLock"}) void unfreezeAppLSP(ProcessRecord app, @UnfreezeReason int reason) { synchronized (mFreezerLock) { - unfreezeAppInternalLSP(app, reason); + unfreezeAppInternalLSP(app, reason, false); } } @@ -1468,8 +1491,7 @@ public final class CachedAppOptimizer { UidRecord uidRec = app.getUidRecord(); if (uidRec != null && uidRec.isFrozen()) { uidRec.setFrozen(false); - mFreezeHandler.removeMessages(UID_FROZEN_STATE_CHANGED_MSG, app); - reportOneUidFrozenStateChanged(app.uid, false); + postUidFrozenMessage(uidRec.getUid(), false); } mFrozenProcesses.delete(app.getPid()); @@ -1998,6 +2020,15 @@ public final class CachedAppOptimizer { mAm.reportUidFrozenStateChanged(uids, frozenStates); } + private void postUidFrozenMessage(int uid, boolean frozen) { + final Integer uidObj = Integer.valueOf(uid); + mFreezeHandler.removeEqualMessages(UID_FROZEN_STATE_CHANGED_MSG, uidObj); + + final int op = frozen ? 1 : 0; + mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage(UID_FROZEN_STATE_CHANGED_MSG, op, + 0, uidObj)); + } + private final class FreezeHandler extends Handler implements ProcLocksReader.ProcLocksReaderCallback { private FreezeHandler() { @@ -2028,7 +2059,9 @@ public final class CachedAppOptimizer { reportUnfreeze(pid, frozenDuration, processName, reason); break; case UID_FROZEN_STATE_CHANGED_MSG: - reportOneUidFrozenStateChanged(((ProcessRecord) msg.obj).uid, true); + final boolean frozen = (msg.arg1 == 1); + final int uid = (int) msg.obj; + reportOneUidFrozenStateChanged(uid, frozen); break; case DEADLOCK_WATCHDOG_MSG: try { @@ -2071,15 +2104,6 @@ public final class CachedAppOptimizer { synchronized (mProcLock) { pid = proc.getPid(); - if (proc.mState.getCurAdj() < ProcessList.CACHED_APP_MIN_ADJ - || opt.shouldNotFreeze()) { - if (DEBUG_FREEZER) { - Slog.d(TAG_AM, "Skipping freeze for process " + pid - + " " + name + " curAdj = " + proc.mState.getCurAdj() - + ", shouldNotFreeze = " + opt.shouldNotFreeze()); - } - return; - } if (mFreezerOverride) { opt.setFreezerOverride(true); @@ -2139,8 +2163,8 @@ public final class CachedAppOptimizer { final UidRecord uidRec = proc.getUidRecord(); if (frozen && uidRec != null && uidRec.areAllProcessesFrozen()) { uidRec.setFrozen(true); - mFreezeHandler.sendMessage(mFreezeHandler.obtainMessage( - UID_FROZEN_STATE_CHANGED_MSG, proc)); + + postUidFrozenMessage(uidRec.getUid(), true); } } diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java index f2331072ce51..e8c8f6dd5462 100644 --- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java +++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java @@ -75,6 +75,15 @@ final class ProcessCachedOptimizerRecord { private boolean mFrozen; /** + * If set to true it will make the (un)freeze decision sticky which means that the freezer + * decision will remain the same unless a freeze is forced via {@link #mForceFreezeOps}. + * This property is usually set to true when external user wants to maintain a (un)frozen state + * after being applied. + */ + @GuardedBy("mProcLock") + private boolean mFreezeSticky; + + /** * Set to false after the process has been frozen. * Set to true after we have collected PSS for the frozen process. */ @@ -195,6 +204,15 @@ final class ProcessCachedOptimizerRecord { void setFrozen(boolean frozen) { mFrozen = frozen; } + @GuardedBy("mProcLock") + void setFreezeSticky(boolean sticky) { + mFreezeSticky = sticky; + } + + @GuardedBy("mProcLock") + boolean isFreezeSticky() { + return mFreezeSticky; + } boolean skipPSSCollectionBecauseFrozen() { boolean collected = mHasCollectedFrozenPSS; diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 7aae4d5b0215..ffb40ee959a4 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -579,7 +579,9 @@ class ProcessRecord implements WindowProcessListener { processName = _processName; sdkSandboxClientAppPackage = _sdkSandboxClientAppPackage; if (isSdkSandbox) { - sdkSandboxClientAppVolumeUuid = getClientInfoForSdkSandbox().volumeUuid; + final ApplicationInfo clientInfo = getClientInfoForSdkSandbox(); + sdkSandboxClientAppVolumeUuid = clientInfo != null + ? clientInfo.volumeUuid : null; } else { sdkSandboxClientAppVolumeUuid = null; } diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java index edf0dbd65ef2..a875860f016f 100644 --- a/services/core/java/com/android/server/am/ServiceRecord.java +++ b/services/core/java/com/android/server/am/ServiceRecord.java @@ -174,6 +174,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN // allow while-in-use permissions in foreground service or not. // while-in-use permissions in FGS started from background might be restricted. boolean mAllowWhileInUsePermissionInFgs; + @PowerExemptionManager.ReasonCode + int mAllowWhileInUsePermissionInFgsReason; // A copy of mAllowWhileInUsePermissionInFgs's value when the service is entering FGS state. boolean mAllowWhileInUsePermissionInFgsAtEntering; /** Allow scheduling user-initiated jobs from the background. */ @@ -609,6 +611,8 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN } pw.print(prefix); pw.print("allowWhileInUsePermissionInFgs="); pw.println(mAllowWhileInUsePermissionInFgs); + pw.print(prefix); pw.print("mAllowWhileInUsePermissionInFgsReason="); + pw.println(mAllowWhileInUsePermissionInFgsReason); pw.print(prefix); pw.print("allowUiJobScheduling="); pw.println(mAllowUiJobScheduling); pw.print(prefix); pw.print("recentCallingPackage="); pw.println(mRecentCallingPackage); diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 12681565f84c..462942e4cb27 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -87,14 +87,14 @@ import java.util.concurrent.atomic.AtomicBoolean; private final @NonNull Context mContext; /** ID for Communication strategy retrieved form audio policy manager */ - private int mCommunicationStrategyId = -1; + /*package*/ int mCommunicationStrategyId = -1; /** ID for Accessibility strategy retrieved form audio policy manager */ private int mAccessibilityStrategyId = -1; /** Active communication device reported by audio policy manager */ - private AudioDeviceInfo mActiveCommunicationDevice; + /*package*/ AudioDeviceInfo mActiveCommunicationDevice; /** Last preferred device set for communication strategy */ private AudioDeviceAttributes mPreferredCommunicationDevice; @@ -204,6 +204,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private void init() { setupMessaging(mContext); + initAudioHalBluetoothState(); initRoutingStrategyIds(); mPreferredCommunicationDevice = null; updateActiveCommunicationDevice(); @@ -749,6 +750,19 @@ import java.util.concurrent.atomic.AtomicBoolean; mIsLeOutput = false; } + BtDeviceInfo(@NonNull BtDeviceInfo src, int state) { + mDevice = src.mDevice; + mState = state; + mProfile = src.mProfile; + mSupprNoisy = src.mSupprNoisy; + mVolume = src.mVolume; + mIsLeOutput = src.mIsLeOutput; + mEventSource = src.mEventSource; + mAudioSystemDevice = src.mAudioSystemDevice; + mMusicDevice = src.mMusicDevice; + mCodec = src.mCodec; + } + // redefine equality op so we can match messages intended for this device @Override public boolean equals(Object o) { @@ -815,7 +829,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * @param info struct with the (dis)connection information */ /*package*/ void queueOnBluetoothActiveDeviceChanged(@NonNull BtDeviceChangedData data) { - if (data.mInfo.getProfile() == BluetoothProfile.A2DP && data.mPreviousDevice != null + if (data.mPreviousDevice != null && data.mPreviousDevice.equals(data.mNewDevice)) { final String name = TextUtils.emptyIfNull(data.mNewDevice.getName()); new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE + MediaMetrics.SEPARATOR @@ -824,7 +838,8 @@ import java.util.concurrent.atomic.AtomicBoolean; .set(MediaMetrics.Property.STATUS, data.mInfo.getProfile()) .record(); synchronized (mDeviceStateLock) { - postBluetoothA2dpDeviceConfigChange(data.mNewDevice); + postBluetoothDeviceConfigChange(createBtDeviceInfo(data, data.mNewDevice, + BluetoothProfile.STATE_CONNECTED)); } } else { synchronized (mDeviceStateLock) { @@ -845,10 +860,100 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - /** - * Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn(). - */ + // Current Bluetooth SCO audio active state indicated by BtHelper via setBluetoothScoOn(). + @GuardedBy("mDeviceStateLock") private boolean mBluetoothScoOn; + // value of BT_SCO parameter currently applied to audio HAL. + @GuardedBy("mDeviceStateLock") + private boolean mBluetoothScoOnApplied; + + // A2DP suspend state requested by AudioManager.setA2dpSuspended() API. + @GuardedBy("mDeviceStateLock") + private boolean mBluetoothA2dpSuspendedExt; + // A2DP suspend state requested by AudioDeviceInventory. + @GuardedBy("mDeviceStateLock") + private boolean mBluetoothA2dpSuspendedInt; + // value of BT_A2dpSuspendedSCO parameter currently applied to audio HAL. + @GuardedBy("mDeviceStateLock") + private boolean mBluetoothA2dpSuspendedApplied; + + // LE Audio suspend state requested by AudioManager.setLeAudioSuspended() API. + @GuardedBy("mDeviceStateLock") + private boolean mBluetoothLeSuspendedExt; + // LE Audio suspend state requested by AudioDeviceInventory. + @GuardedBy("mDeviceStateLock") + private boolean mBluetoothLeSuspendedInt; + // value of LeAudioSuspended parameter currently applied to audio HAL. + @GuardedBy("mDeviceStateLock") + private boolean mBluetoothLeSuspendedApplied; + + private void initAudioHalBluetoothState() { + mBluetoothScoOnApplied = false; + AudioSystem.setParameters("BT_SCO=off"); + mBluetoothA2dpSuspendedApplied = false; + AudioSystem.setParameters("A2dpSuspended=false"); + mBluetoothLeSuspendedApplied = false; + AudioSystem.setParameters("LeAudioSuspended=false"); + } + + @GuardedBy("mDeviceStateLock") + private void updateAudioHalBluetoothState() { + if (mBluetoothScoOn != mBluetoothScoOnApplied) { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothScoOn: " + + mBluetoothScoOn + ", mBluetoothScoOnApplied: " + mBluetoothScoOnApplied); + } + if (mBluetoothScoOn) { + if (!mBluetoothA2dpSuspendedApplied) { + AudioSystem.setParameters("A2dpSuspended=true"); + mBluetoothA2dpSuspendedApplied = true; + } + if (!mBluetoothLeSuspendedApplied) { + AudioSystem.setParameters("LeAudioSuspended=true"); + mBluetoothLeSuspendedApplied = true; + } + AudioSystem.setParameters("BT_SCO=on"); + } else { + AudioSystem.setParameters("BT_SCO=off"); + } + mBluetoothScoOnApplied = mBluetoothScoOn; + } + if (!mBluetoothScoOnApplied) { + if ((mBluetoothA2dpSuspendedExt || mBluetoothA2dpSuspendedInt) + != mBluetoothA2dpSuspendedApplied) { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothA2dpSuspendedExt: " + + mBluetoothA2dpSuspendedExt + + ", mBluetoothA2dpSuspendedInt: " + mBluetoothA2dpSuspendedInt + + ", mBluetoothA2dpSuspendedApplied: " + + mBluetoothA2dpSuspendedApplied); + } + mBluetoothA2dpSuspendedApplied = + mBluetoothA2dpSuspendedExt || mBluetoothA2dpSuspendedInt; + if (mBluetoothA2dpSuspendedApplied) { + AudioSystem.setParameters("A2dpSuspended=true"); + } else { + AudioSystem.setParameters("A2dpSuspended=false"); + } + } + if ((mBluetoothLeSuspendedExt || mBluetoothLeSuspendedInt) + != mBluetoothLeSuspendedApplied) { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "updateAudioHalBluetoothState() mBluetoothLeSuspendedExt: " + + mBluetoothLeSuspendedExt + + ", mBluetoothLeSuspendedInt: " + mBluetoothLeSuspendedInt + + ", mBluetoothLeSuspendedApplied: " + mBluetoothLeSuspendedApplied); + } + mBluetoothLeSuspendedApplied = + mBluetoothLeSuspendedExt || mBluetoothLeSuspendedInt; + if (mBluetoothLeSuspendedApplied) { + AudioSystem.setParameters("LeAudioSuspended=true"); + } else { + AudioSystem.setParameters("LeAudioSuspended=false"); + } + } + } + } /*package*/ void setBluetoothScoOn(boolean on, String eventSource) { if (AudioService.DEBUG_COMM_RTE) { @@ -856,10 +961,67 @@ import java.util.concurrent.atomic.AtomicBoolean; } synchronized (mDeviceStateLock) { mBluetoothScoOn = on; + updateAudioHalBluetoothState(); postUpdateCommunicationRouteClient(eventSource); } } + /*package*/ void setA2dpSuspended(boolean enable, boolean internal, String eventSource) { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "setA2dpSuspended source: " + eventSource + ", enable: " + + enable + ", internal: " + internal + + ", mBluetoothA2dpSuspendedInt: " + mBluetoothA2dpSuspendedInt + + ", mBluetoothA2dpSuspendedExt: " + mBluetoothA2dpSuspendedExt); + } + synchronized (mDeviceStateLock) { + if (internal) { + mBluetoothA2dpSuspendedInt = enable; + } else { + mBluetoothA2dpSuspendedExt = enable; + } + updateAudioHalBluetoothState(); + } + } + + /*package*/ void clearA2dpSuspended() { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "clearA2dpSuspended"); + } + synchronized (mDeviceStateLock) { + mBluetoothA2dpSuspendedInt = false; + mBluetoothA2dpSuspendedExt = false; + updateAudioHalBluetoothState(); + } + } + + /*package*/ void setLeAudioSuspended(boolean enable, boolean internal, String eventSource) { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "setLeAudioSuspended source: " + eventSource + ", enable: " + + enable + ", internal: " + internal + + ", mBluetoothLeSuspendedInt: " + mBluetoothA2dpSuspendedInt + + ", mBluetoothLeSuspendedExt: " + mBluetoothA2dpSuspendedExt); + } + synchronized (mDeviceStateLock) { + if (internal) { + mBluetoothLeSuspendedInt = enable; + } else { + mBluetoothLeSuspendedExt = enable; + } + updateAudioHalBluetoothState(); + } + } + + /*package*/ void clearLeAudioSuspended() { + if (AudioService.DEBUG_COMM_RTE) { + Log.v(TAG, "clearLeAudioSuspended"); + } + synchronized (mDeviceStateLock) { + mBluetoothLeSuspendedInt = false; + mBluetoothLeSuspendedExt = false; + updateAudioHalBluetoothState(); + } + } + /*package*/ AudioRoutesInfo startWatchingRoutes(IAudioRoutesObserver observer) { synchronized (mDeviceStateLock) { return mDeviceInventory.startWatchingRoutes(observer); @@ -902,8 +1064,8 @@ import java.util.concurrent.atomic.AtomicBoolean; new AudioModeInfo(mode, pid, uid)); } - /*package*/ void postBluetoothA2dpDeviceConfigChange(@NonNull BluetoothDevice device) { - sendLMsgNoDelay(MSG_L_A2DP_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, device); + /*package*/ void postBluetoothDeviceConfigChange(@NonNull BtDeviceInfo info) { + sendLMsgNoDelay(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE, SENDMSG_QUEUE, info); } /*package*/ void startBluetoothScoForClient(IBinder cb, int pid, int scoAudioMode, @@ -1160,6 +1322,10 @@ import java.util.concurrent.atomic.AtomicBoolean; sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state); } + /*package*/ void postNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) { + sendLMsgNoDelay(MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED, SENDMSG_QUEUE, btDevice); + } + /*package*/ static final class CommunicationDeviceInfo { final @NonNull IBinder mCb; // Identifies the requesting client for death handler final int mPid; // Requester process ID @@ -1235,9 +1401,11 @@ import java.util.concurrent.atomic.AtomicBoolean; } } - /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect) { + /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, + boolean connect, @Nullable BluetoothDevice btDevice) { synchronized (mDeviceStateLock) { - return mDeviceInventory.handleDeviceConnection(attributes, connect, false /*for test*/); + return mDeviceInventory.handleDeviceConnection( + attributes, connect, false /*for test*/, btDevice); } } @@ -1478,13 +1646,10 @@ import java.util.concurrent.atomic.AtomicBoolean; (String) msg.obj, msg.arg1); } break; - case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: - final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; + case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: synchronized (mDeviceStateLock) { - final int a2dpCodec = mBtHelper.getA2dpCodec(btDevice); - mDeviceInventory.onBluetoothA2dpDeviceConfigChange( - new BtHelper.BluetoothA2dpDeviceInfo(btDevice, -1, a2dpCodec), - BtHelper.EVENT_DEVICE_CONFIG_CHANGE); + mDeviceInventory.onBluetoothDeviceConfigChange( + (BtDeviceInfo) msg.obj, BtHelper.EVENT_DEVICE_CONFIG_CHANGE); } break; case MSG_BROADCAST_AUDIO_BECOMING_NOISY: @@ -1642,6 +1807,10 @@ import java.util.concurrent.atomic.AtomicBoolean; final int capturePreset = msg.arg1; mDeviceInventory.onSaveClearPreferredDevicesForCapturePreset(capturePreset); } break; + case MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED: { + final BluetoothDevice btDevice = (BluetoothDevice) msg.obj; + BtHelper.onNotifyPreferredAudioProfileApplied(btDevice); + } break; default: Log.wtf(TAG, "Invalid message " + msg.what); } @@ -1677,7 +1846,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_IL_BTA2DP_TIMEOUT = 10; // process change of A2DP device configuration, obj is BluetoothDevice - private static final int MSG_L_A2DP_DEVICE_CONFIG_CHANGE = 11; + private static final int MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE = 11; private static final int MSG_BROADCAST_AUDIO_BECOMING_NOISY = 12; private static final int MSG_REPORT_NEW_ROUTES = 13; @@ -1717,13 +1886,15 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MSG_IL_SAVE_REMOVE_NDEF_DEVICE_FOR_STRATEGY = 48; private static final int MSG_IL_BTLEAUDIO_TIMEOUT = 49; + private static final int MSG_L_NOTIFY_PREFERRED_AUDIOPROFILE_APPLIED = 50; + private static boolean isMessageHandledUnderWakelock(int msgId) { switch(msgId) { case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_L_SET_BT_ACTIVE_DEVICE: case MSG_IL_BTA2DP_TIMEOUT: case MSG_IL_BTLEAUDIO_TIMEOUT: - case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: case MSG_TOGGLE_HDMI: case MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT: case MSG_L_HEARING_AID_DEVICE_CONNECTION_CHANGE_EXT: @@ -1815,7 +1986,7 @@ import java.util.concurrent.atomic.AtomicBoolean; case MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE: case MSG_IL_BTA2DP_TIMEOUT: case MSG_IL_BTLEAUDIO_TIMEOUT: - case MSG_L_A2DP_DEVICE_CONFIG_CHANGE: + case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: if (sLastDeviceConnectMsgTime >= time) { // add a little delay to make sure messages are ordered as expected time = sLastDeviceConnectMsgTime + 30; @@ -1835,7 +2006,7 @@ import java.util.concurrent.atomic.AtomicBoolean; static { MESSAGES_MUTE_MUSIC = new HashSet<>(); MESSAGES_MUTE_MUSIC.add(MSG_L_SET_BT_ACTIVE_DEVICE); - MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONFIG_CHANGE); + MESSAGES_MUTE_MUSIC.add(MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE); MESSAGES_MUTE_MUSIC.add(MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT); MESSAGES_MUTE_MUSIC.add(MSG_IIL_SET_FORCE_BT_A2DP_USE); } @@ -1856,7 +2027,7 @@ import java.util.concurrent.atomic.AtomicBoolean; // Do not mute on bluetooth event if music is playing on a wired headset. if ((message == MSG_L_SET_BT_ACTIVE_DEVICE || message == MSG_L_A2DP_DEVICE_CONNECTION_CHANGE_EXT - || message == MSG_L_A2DP_DEVICE_CONFIG_CHANGE) + || message == MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE) && AudioSystem.isStreamActive(AudioSystem.STREAM_MUSIC, 0) && hasIntersection(mDeviceInventory.DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET, mAudioService.getDeviceSetForStream(AudioSystem.STREAM_MUSIC))) { @@ -1992,27 +2163,22 @@ import java.util.concurrent.atomic.AtomicBoolean; "updateCommunicationRoute, preferredCommunicationDevice: " + preferredCommunicationDevice + " eventSource: " + eventSource))); - if (preferredCommunicationDevice == null - || preferredCommunicationDevice.getType() != AudioDeviceInfo.TYPE_BLUETOOTH_SCO) { - AudioSystem.setParameters("BT_SCO=off"); - } else { - AudioSystem.setParameters("BT_SCO=on"); - } if (preferredCommunicationDevice == null) { AudioDeviceAttributes defaultDevice = getDefaultCommunicationDevice(); if (defaultDevice != null) { - setPreferredDevicesForStrategySync( + mDeviceInventory.setPreferredDevicesForStrategy( mCommunicationStrategyId, Arrays.asList(defaultDevice)); - setPreferredDevicesForStrategySync( + mDeviceInventory.setPreferredDevicesForStrategy( mAccessibilityStrategyId, Arrays.asList(defaultDevice)); } else { - removePreferredDevicesForStrategySync(mCommunicationStrategyId); - removePreferredDevicesForStrategySync(mAccessibilityStrategyId); + mDeviceInventory.removePreferredDevicesForStrategy(mCommunicationStrategyId); + mDeviceInventory.removePreferredDevicesForStrategy(mAccessibilityStrategyId); } + mDeviceInventory.applyConnectedDevicesRoles(); } else { - setPreferredDevicesForStrategySync( + mDeviceInventory.setPreferredDevicesForStrategy( mCommunicationStrategyId, Arrays.asList(preferredCommunicationDevice)); - setPreferredDevicesForStrategySync( + mDeviceInventory.setPreferredDevicesForStrategy( mAccessibilityStrategyId, Arrays.asList(preferredCommunicationDevice)); } onUpdatePhoneStrategyDevice(preferredCommunicationDevice); diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 43063afb4ce7..1eb39f7352af 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -34,26 +34,36 @@ import android.media.ICapturePresetDevicesRoleDispatcher; import android.media.IStrategyNonDefaultDevicesDispatcher; import android.media.IStrategyPreferredDevicesDispatcher; import android.media.MediaMetrics; +import android.media.MediaRecorder.AudioSource; +import android.media.audiopolicy.AudioProductStrategy; import android.media.permission.ClearCallingIdentityContext; import android.media.permission.SafeCloseable; import android.os.Binder; +import android.os.Bundle; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.SystemProperties; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.utils.EventLogger; +import com.google.android.collect.Sets; + import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashSet; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -175,18 +185,26 @@ public class AudioDeviceInventory { final RemoteCallbackList<ICapturePresetDevicesRoleDispatcher> mDevRoleCapturePresetDispatchers = new RemoteCallbackList<ICapturePresetDevicesRoleDispatcher>(); + final List<AudioProductStrategy> mStrategies; + /*package*/ AudioDeviceInventory(@NonNull AudioDeviceBroker broker) { - mDeviceBroker = broker; - mAudioSystem = AudioSystemAdapter.getDefaultAdapter(); + this(broker, AudioSystemAdapter.getDefaultAdapter()); } //----------------------------------------------------------- /** for mocking only, allows to inject AudioSystem adapter */ /*package*/ AudioDeviceInventory(@NonNull AudioSystemAdapter audioSystem) { - mDeviceBroker = null; - mAudioSystem = audioSystem; + this(null, audioSystem); } + private AudioDeviceInventory(@Nullable AudioDeviceBroker broker, + @Nullable AudioSystemAdapter audioSystem) { + mDeviceBroker = broker; + mAudioSystem = audioSystem; + mStrategies = AudioProductStrategy.getAudioProductStrategies(); + mBluetoothDualModeEnabled = SystemProperties.getBoolean( + "persist.bluetooth.enable_dual_mode_audio", false); + } /*package*/ void setDeviceBroker(@NonNull AudioDeviceBroker broker) { mDeviceBroker = broker; } @@ -203,8 +221,13 @@ public class AudioDeviceInventory { int mDeviceCodecFormat; final UUID mSensorUuid; + /** Disabled operating modes for this device. Use a negative logic so that by default + * an empty list means all modes are allowed. + * See BluetoothAdapter.AUDIO_MODE_DUPLEX and BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY */ + @NonNull ArraySet<String> mDisabledModes = new ArraySet(0); + DeviceInfo(int deviceType, String deviceName, String deviceAddress, - int deviceCodecFormat, UUID sensorUuid) { + int deviceCodecFormat, @Nullable UUID sensorUuid) { mDeviceType = deviceType; mDeviceName = deviceName == null ? "" : deviceName; mDeviceAddress = deviceAddress == null ? "" : deviceAddress; @@ -212,11 +235,31 @@ public class AudioDeviceInventory { mSensorUuid = sensorUuid; } + void setModeDisabled(String mode) { + mDisabledModes.add(mode); + } + void setModeEnabled(String mode) { + mDisabledModes.remove(mode); + } + boolean isModeEnabled(String mode) { + return !mDisabledModes.contains(mode); + } + boolean isOutputOnlyModeEnabled() { + return isModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY); + } + boolean isDuplexModeEnabled() { + return isModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX); + } + DeviceInfo(int deviceType, String deviceName, String deviceAddress, int deviceCodecFormat) { this(deviceType, deviceName, deviceAddress, deviceCodecFormat, null); } + DeviceInfo(int deviceType, String deviceName, String deviceAddress) { + this(deviceType, deviceName, deviceAddress, AudioSystem.AUDIO_FORMAT_DEFAULT); + } + @Override public String toString() { return "[DeviceInfo: type:0x" + Integer.toHexString(mDeviceType) @@ -224,7 +267,8 @@ public class AudioDeviceInventory { + ") name:" + mDeviceName + " addr:" + mDeviceAddress + " codec: " + Integer.toHexString(mDeviceCodecFormat) - + " sensorUuid: " + Objects.toString(mSensorUuid) + "]"; + + " sensorUuid: " + Objects.toString(mSensorUuid) + + " disabled modes: " + mDisabledModes + "]"; } @NonNull String getKey() { @@ -276,9 +320,18 @@ public class AudioDeviceInventory { pw.println(" " + prefix + " type:0x" + Integer.toHexString(keyType) + " (" + AudioSystem.getDeviceName(keyType) + ") addr:" + valueAddress); }); + pw.println("\n" + prefix + "Preferred devices for capture preset:"); mPreferredDevicesForCapturePreset.forEach((capturePreset, devices) -> { pw.println(" " + prefix + "capturePreset:" + capturePreset + " devices:" + devices); }); + pw.println("\n" + prefix + "Applied devices roles for strategies:"); + mAppliedStrategyRoles.forEach((key, devices) -> { + pw.println(" " + prefix + "strategy: " + key.first + + " role:" + key.second + " devices:" + devices); }); + pw.println("\n" + prefix + "Applied devices roles for presets:"); + mAppliedPresetRoles.forEach((key, devices) -> { + pw.println(" " + prefix + "preset: " + key.first + + " role:" + key.second + " devices:" + devices); }); } //------------------------------------------------------------ @@ -299,15 +352,16 @@ public class AudioDeviceInventory { AudioSystem.DEVICE_STATE_AVAILABLE, di.mDeviceCodecFormat); } + mAppliedStrategyRoles.clear(); + applyConnectedDevicesRoles_l(); } synchronized (mPreferredDevices) { mPreferredDevices.forEach((strategy, devices) -> { - mAudioSystem.setDevicesRoleForStrategy( - strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); }); + setPreferredDevicesForStrategy(strategy, devices); }); } synchronized (mNonDefaultDevices) { mNonDefaultDevices.forEach((strategy, devices) -> { - mAudioSystem.setDevicesRoleForStrategy( + addDevicesRoleForStrategy( strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices); }); } synchronized (mPreferredDevicesForCapturePreset) { @@ -380,8 +434,7 @@ public class AudioDeviceInventory { btInfo.mVolume * 10, btInfo.mAudioSystemDevice, "onSetBtActiveDevice"); } - makeA2dpDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), - "onSetBtActiveDevice", btInfo.mCodec); + makeA2dpDeviceAvailable(btInfo, "onSetBtActiveDevice"); } break; case BluetoothProfile.HEARING_AID: @@ -397,10 +450,7 @@ public class AudioDeviceInventory { if (switchToUnavailable) { makeLeAudioDeviceUnavailableNow(address, btInfo.mAudioSystemDevice); } else if (switchToAvailable) { - makeLeAudioDeviceAvailable(address, BtHelper.getName(btInfo.mDevice), - streamType, btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10, - btInfo.mAudioSystemDevice, - "onSetBtActiveDevice"); + makeLeAudioDeviceAvailable(btInfo, streamType, "onSetBtActiveDevice"); } break; default: throw new IllegalArgumentException("Invalid profile " @@ -411,30 +461,30 @@ public class AudioDeviceInventory { @GuardedBy("AudioDeviceBroker.mDeviceStateLock") - /*package*/ void onBluetoothA2dpDeviceConfigChange( - @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) { + /*package*/ void onBluetoothDeviceConfigChange( + @NonNull AudioDeviceBroker.BtDeviceInfo btInfo, int event) { MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId - + "onBluetoothA2dpDeviceConfigChange") - .set(MediaMetrics.Property.EVENT, BtHelper.a2dpDeviceEventToString(event)); + + "onBluetoothDeviceConfigChange") + .set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event)); - final BluetoothDevice btDevice = btInfo.getBtDevice(); + final BluetoothDevice btDevice = btInfo.mDevice; if (btDevice == null) { mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record(); return; } if (AudioService.DEBUG_DEVICES) { - Log.d(TAG, "onBluetoothA2dpDeviceConfigChange btDevice=" + btDevice); + Log.d(TAG, "onBluetoothDeviceConfigChange btDevice=" + btDevice); } - int a2dpVolume = btInfo.getVolume(); - @AudioSystem.AudioFormatNativeEnumForBtCodec final int a2dpCodec = btInfo.getCodec(); + int volume = btInfo.mVolume; + @AudioSystem.AudioFormatNativeEnumForBtCodec final int audioCodec = btInfo.mCodec; String address = btDevice.getAddress(); if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "onBluetoothA2dpDeviceConfigChange addr=" + address - + " event=" + BtHelper.a2dpDeviceEventToString(event))); + "onBluetoothDeviceConfigChange addr=" + address + + " event=" + BtHelper.deviceEventToString(event))); synchronized (mDevicesLock) { if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) { @@ -449,53 +499,54 @@ public class AudioDeviceInventory { AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); final DeviceInfo di = mConnectedDevices.get(key); if (di == null) { - Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpDeviceConfigChange"); + Log.e(TAG, "invalid null DeviceInfo in onBluetoothDeviceConfigChange"); mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record(); return; } mmi.set(MediaMetrics.Property.ADDRESS, address) .set(MediaMetrics.Property.ENCODING, - AudioSystem.audioFormatToString(a2dpCodec)) - .set(MediaMetrics.Property.INDEX, a2dpVolume) + AudioSystem.audioFormatToString(audioCodec)) + .set(MediaMetrics.Property.INDEX, volume) .set(MediaMetrics.Property.NAME, di.mDeviceName); - if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) { - // Device is connected - if (a2dpVolume != -1) { - mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC, - // convert index to internal representation in VolumeStreamState - a2dpVolume * 10, - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - "onBluetoothA2dpDeviceConfigChange"); - } - } else if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) { - if (di.mDeviceCodecFormat != a2dpCodec) { - di.mDeviceCodecFormat = a2dpCodec; - mConnectedDevices.replace(key, di); - } - } - final int res = mAudioSystem.handleDeviceConfigChange( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, - BtHelper.getName(btDevice), a2dpCodec); - if (res != AudioSystem.AUDIO_STATUS_OK) { - AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "APM handleDeviceConfigChange failed for A2DP device addr=" + address - + " codec=" + AudioSystem.audioFormatToString(a2dpCodec)) - .printLog(TAG)); + if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) { + boolean a2dpCodecChange = false; + if (btInfo.mProfile == BluetoothProfile.A2DP) { + if (di.mDeviceCodecFormat != audioCodec) { + di.mDeviceCodecFormat = audioCodec; + mConnectedDevices.replace(key, di); + a2dpCodecChange = true; + } + final int res = mAudioSystem.handleDeviceConfigChange( + btInfo.mAudioSystemDevice, address, + BtHelper.getName(btDevice), audioCodec); + + if (res != AudioSystem.AUDIO_STATUS_OK) { + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "APM handleDeviceConfigChange failed for A2DP device addr=" + + address + " codec=" + + AudioSystem.audioFormatToString(audioCodec)) + .printLog(TAG)); + + // force A2DP device disconnection in case of error so that AudioService + // state is consistent with audio policy manager state + setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btInfo, + BluetoothProfile.STATE_DISCONNECTED)); + } else { + AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( + "APM handleDeviceConfigChange success for A2DP device addr=" + + address + + " codec=" + AudioSystem.audioFormatToString(audioCodec)) + .printLog(TAG)); - int musicDevice = mDeviceBroker.getDeviceForStream(AudioSystem.STREAM_MUSIC); - // force A2DP device disconnection in case of error so that AudioService state is - // consistent with audio policy manager state - setBluetoothActiveDevice(new AudioDeviceBroker.BtDeviceInfo(btDevice, - BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED, - musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP)); - } else { - AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( - "APM handleDeviceConfigChange success for A2DP device addr=" + address - + " codec=" + AudioSystem.audioFormatToString(a2dpCodec)) - .printLog(TAG)); + } + } + if (!a2dpCodecChange) { + updateBluetoothPreferredModes_l(); + mDeviceBroker.postNotifyPreferredAudioProfileApplied(btDevice); + } } } mmi.record(); @@ -578,7 +629,7 @@ public class AudioDeviceInventory { } if (!handleDeviceConnection(wdcs.mAttributes, - wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest)) { + wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED, wdcs.mForTest, null)) { // change of connection state failed, bailout mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed") .record(); @@ -712,23 +763,35 @@ public class AudioDeviceInventory { /*package*/ int setPreferredDevicesForStrategySync(int strategy, @NonNull List<AudioDeviceAttributes> devices) { - int status = AudioSystem.ERROR; + final int status = setPreferredDevicesForStrategy(strategy, devices); + if (status == AudioSystem.SUCCESS) { + mDeviceBroker.postSaveSetPreferredDevicesForStrategy(strategy, devices); + } + return status; + } + /*package*/ int setPreferredDevicesForStrategy(int strategy, + @NonNull List<AudioDeviceAttributes> devices) { + int status = AudioSystem.ERROR; try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( - "setPreferredDevicesForStrategySync, strategy: " + strategy + "setPreferredDevicesForStrategy, strategy: " + strategy + " devices: " + devices)).printLog(TAG)); - status = mAudioSystem.setDevicesRoleForStrategy( + status = setDevicesRoleForStrategy( strategy, AudioSystem.DEVICE_ROLE_PREFERRED, devices); } + return status; + } + /*package*/ int removePreferredDevicesForStrategySync(int strategy) { + final int status = removePreferredDevicesForStrategy(strategy); if (status == AudioSystem.SUCCESS) { - mDeviceBroker.postSaveSetPreferredDevicesForStrategy(strategy, devices); + mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy); } return status; } - /*package*/ int removePreferredDevicesForStrategySync(int strategy) { + /*package*/ int removePreferredDevicesForStrategy(int strategy) { int status = AudioSystem.ERROR; try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { @@ -736,13 +799,9 @@ public class AudioDeviceInventory { "removePreferredDevicesForStrategySync, strategy: " + strategy)).printLog(TAG)); - status = mAudioSystem.clearDevicesRoleForStrategy( + status = clearDevicesRoleForStrategy( strategy, AudioSystem.DEVICE_ROLE_PREFERRED); } - - if (status == AudioSystem.SUCCESS) { - mDeviceBroker.postSaveRemovePreferredDevicesForStrategy(strategy); - } return status; } @@ -757,7 +816,7 @@ public class AudioDeviceInventory { AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent( "setDeviceAsNonDefaultForStrategySync, strategy: " + strategy + " device: " + device)).printLog(TAG)); - status = mAudioSystem.setDevicesRoleForStrategy( + status = addDevicesRoleForStrategy( strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices); } @@ -779,7 +838,7 @@ public class AudioDeviceInventory { "removeDeviceAsNonDefaultForStrategySync, strategy: " + strategy + " devices: " + device)).printLog(TAG)); - status = mAudioSystem.removeDevicesRoleForStrategy( + status = removeDevicesRoleForStrategy( strategy, AudioSystem.DEVICE_ROLE_DISABLED, devices); } @@ -812,33 +871,70 @@ public class AudioDeviceInventory { /*package*/ int setPreferredDevicesForCapturePresetSync( int capturePreset, @NonNull List<AudioDeviceAttributes> devices) { - int status = AudioSystem.ERROR; + final int status = setPreferredDevicesForCapturePreset(capturePreset, devices); + if (status == AudioSystem.SUCCESS) { + mDeviceBroker.postSaveSetPreferredDevicesForCapturePreset(capturePreset, devices); + } + return status; + } + private int setPreferredDevicesForCapturePreset( + int capturePreset, @NonNull List<AudioDeviceAttributes> devices) { + int status = AudioSystem.ERROR; try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { - status = mAudioSystem.setDevicesRoleForCapturePreset( + status = setDevicesRoleForCapturePreset( capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED, devices); } + return status; + } + /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) { + final int status = clearPreferredDevicesForCapturePreset(capturePreset); if (status == AudioSystem.SUCCESS) { - mDeviceBroker.postSaveSetPreferredDevicesForCapturePreset(capturePreset, devices); + mDeviceBroker.postSaveClearPreferredDevicesForCapturePreset(capturePreset); } return status; } - /*package*/ int clearPreferredDevicesForCapturePresetSync(int capturePreset) { + private int clearPreferredDevicesForCapturePreset(int capturePreset) { int status = AudioSystem.ERROR; try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { - status = mAudioSystem.clearDevicesRoleForCapturePreset( + status = clearDevicesRoleForCapturePreset( capturePreset, AudioSystem.DEVICE_ROLE_PREFERRED); } - - if (status == AudioSystem.SUCCESS) { - mDeviceBroker.postSaveClearPreferredDevicesForCapturePreset(capturePreset); - } return status; } + private int addDevicesRoleForCapturePreset(int capturePreset, int role, + @NonNull List<AudioDeviceAttributes> devices) { + return addDevicesRole(mAppliedPresetRoles, (p, r, d) -> { + return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d); + }, capturePreset, role, devices); + } + + private int removeDevicesRoleForCapturePreset(int capturePreset, int role, + @NonNull List<AudioDeviceAttributes> devices) { + return removeDevicesRole(mAppliedPresetRoles, (p, r, d) -> { + return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); + }, capturePreset, role, devices); + } + + private int setDevicesRoleForCapturePreset(int capturePreset, int role, + @NonNull List<AudioDeviceAttributes> devices) { + return setDevicesRole(mAppliedPresetRoles, (p, r, d) -> { + return mAudioSystem.addDevicesRoleForCapturePreset(p, r, d); + }, (p, r, d) -> { + return mAudioSystem.clearDevicesRoleForCapturePreset(p, r); + }, capturePreset, role, devices); + } + + private int clearDevicesRoleForCapturePreset(int capturePreset, int role) { + return clearDevicesRole(mAppliedPresetRoles, (p, r, d) -> { + return mAudioSystem.clearDevicesRoleForCapturePreset(p, r); + }, capturePreset, role); + } + /*package*/ void registerCapturePresetDevicesRoleDispatcher( @NonNull ICapturePresetDevicesRoleDispatcher dispatcher) { mDevRoleCapturePresetDispatchers.register(dispatcher); @@ -849,7 +945,208 @@ public class AudioDeviceInventory { mDevRoleCapturePresetDispatchers.unregister(dispatcher); } - //----------------------------------------------------------------------- + private int addDevicesRoleForStrategy(int strategy, int role, + @NonNull List<AudioDeviceAttributes> devices) { + return addDevicesRole(mAppliedStrategyRoles, (s, r, d) -> { + return mAudioSystem.setDevicesRoleForStrategy(s, r, d); + }, strategy, role, devices); + } + + private int removeDevicesRoleForStrategy(int strategy, int role, + @NonNull List<AudioDeviceAttributes> devices) { + return removeDevicesRole(mAppliedStrategyRoles, (s, r, d) -> { + return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); + }, strategy, role, devices); + } + + private int setDevicesRoleForStrategy(int strategy, int role, + @NonNull List<AudioDeviceAttributes> devices) { + return setDevicesRole(mAppliedStrategyRoles, (s, r, d) -> { + return mAudioSystem.setDevicesRoleForStrategy(s, r, d); + }, (s, r, d) -> { + return mAudioSystem.clearDevicesRoleForStrategy(s, r); + }, strategy, role, devices); + } + + private int clearDevicesRoleForStrategy(int strategy, int role) { + return clearDevicesRole(mAppliedStrategyRoles, (s, r, d) -> { + return mAudioSystem.clearDevicesRoleForStrategy(s, r); + }, strategy, role); + } + + //------------------------------------------------------------ + // Cache for applied roles for strategies and devices. The cache avoids reapplying the + // same list of devices for a given role and strategy and the corresponding systematic + // redundant work in audio policy manager and audio flinger. + // The key is the pair <Strategy , Role> and the value is the current list of devices. + + private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> + mAppliedStrategyRoles = new ArrayMap<>(); + + // Cache for applied roles for capture presets and devices. The cache avoids reapplying the + // same list of devices for a given role and capture preset and the corresponding systematic + // redundant work in audio policy manager and audio flinger. + // The key is the pair <Preset , Role> and the value is the current list of devices. + private final ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> + mAppliedPresetRoles = new ArrayMap<>(); + + interface AudioSystemInterface { + int deviceRoleAction(int usecase, int role, @Nullable List<AudioDeviceAttributes> devices); + } + + private int addDevicesRole( + ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap, + AudioSystemInterface asi, + int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) { + synchronized (rolesMap) { + Pair<Integer, Integer> key = new Pair<>(useCase, role); + List<AudioDeviceAttributes> roleDevices = new ArrayList<>(); + List<AudioDeviceAttributes> appliedDevices = new ArrayList<>(); + + if (rolesMap.containsKey(key)) { + roleDevices = rolesMap.get(key); + for (AudioDeviceAttributes device : devices) { + if (!roleDevices.contains(device)) { + appliedDevices.add(device); + } + } + } else { + appliedDevices.addAll(devices); + } + if (appliedDevices.isEmpty()) { + return AudioSystem.SUCCESS; + } + final int status = asi.deviceRoleAction(useCase, role, appliedDevices); + if (status == AudioSystem.SUCCESS) { + roleDevices.addAll(appliedDevices); + rolesMap.put(key, roleDevices); + } + return status; + } + } + + private int removeDevicesRole( + ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap, + AudioSystemInterface asi, + int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) { + synchronized (rolesMap) { + Pair<Integer, Integer> key = new Pair<>(useCase, role); + if (!rolesMap.containsKey(key)) { + return AudioSystem.SUCCESS; + } + List<AudioDeviceAttributes> roleDevices = rolesMap.get(key); + List<AudioDeviceAttributes> appliedDevices = new ArrayList<>(); + for (AudioDeviceAttributes device : devices) { + if (roleDevices.contains(device)) { + appliedDevices.add(device); + } + } + if (appliedDevices.isEmpty()) { + return AudioSystem.SUCCESS; + } + final int status = asi.deviceRoleAction(useCase, role, appliedDevices); + if (status == AudioSystem.SUCCESS) { + roleDevices.removeAll(appliedDevices); + if (roleDevices.isEmpty()) { + rolesMap.remove(key); + } else { + rolesMap.put(key, roleDevices); + } + } + return status; + } + } + + private int setDevicesRole( + ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap, + AudioSystemInterface addOp, + AudioSystemInterface clearOp, + int useCase, int role, @NonNull List<AudioDeviceAttributes> devices) { + synchronized (rolesMap) { + Pair<Integer, Integer> key = new Pair<>(useCase, role); + List<AudioDeviceAttributes> roleDevices = new ArrayList<>(); + List<AudioDeviceAttributes> appliedDevices = new ArrayList<>(); + + if (rolesMap.containsKey(key)) { + roleDevices = rolesMap.get(key); + boolean equal = false; + if (roleDevices.size() == devices.size()) { + roleDevices.retainAll(devices); + equal = roleDevices.size() == devices.size(); + } + if (!equal) { + clearOp.deviceRoleAction(useCase, role, null); + roleDevices.clear(); + appliedDevices.addAll(devices); + } + } else { + appliedDevices.addAll(devices); + } + if (appliedDevices.isEmpty()) { + return AudioSystem.SUCCESS; + } + final int status = addOp.deviceRoleAction(useCase, role, appliedDevices); + if (status == AudioSystem.SUCCESS) { + roleDevices.addAll(appliedDevices); + rolesMap.put(key, roleDevices); + } + return status; + } + } + + private int clearDevicesRole( + ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap, + AudioSystemInterface asi, int useCase, int role) { + synchronized (rolesMap) { + Pair<Integer, Integer> key = new Pair<>(useCase, role); + if (!rolesMap.containsKey(key)) { + return AudioSystem.SUCCESS; + } + final int status = asi.deviceRoleAction(useCase, role, null); + if (status == AudioSystem.SUCCESS) { + rolesMap.remove(key); + } + return status; + } + } + + @GuardedBy("mDevicesLock") + private void purgeDevicesRoles_l() { + purgeRoles(mAppliedStrategyRoles, (s, r, d) -> { + return mAudioSystem.removeDevicesRoleForStrategy(s, r, d); }); + purgeRoles(mAppliedPresetRoles, (p, r, d) -> { + return mAudioSystem.removeDevicesRoleForCapturePreset(p, r, d); }); + } + + @GuardedBy("mDevicesLock") + private void purgeRoles( + ArrayMap<Pair<Integer, Integer>, List<AudioDeviceAttributes>> rolesMap, + AudioSystemInterface asi) { + synchronized (rolesMap) { + Iterator<Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>>> itRole = + rolesMap.entrySet().iterator(); + while (itRole.hasNext()) { + Map.Entry<Pair<Integer, Integer>, List<AudioDeviceAttributes>> entry = + itRole.next(); + Pair<Integer, Integer> keyRole = entry.getKey(); + Iterator<AudioDeviceAttributes> itDev = rolesMap.get(keyRole).iterator(); + while (itDev.hasNext()) { + AudioDeviceAttributes ada = itDev.next(); + final String devKey = DeviceInfo.makeDeviceListKey(ada.getInternalType(), + ada.getAddress()); + if (mConnectedDevices.get(devKey) == null) { + asi.deviceRoleAction(keyRole.first, keyRole.second, Arrays.asList(ada)); + itDev.remove(); + } + } + if (rolesMap.get(keyRole).isEmpty()) { + itRole.remove(); + } + } + } + } + +//----------------------------------------------------------------------- /** * Check if a device is in the list of connected devices @@ -871,10 +1168,11 @@ public class AudioDeviceInventory { * @param connect true if connection * @param isForTesting if true, not calling AudioSystem for the connection as this is * just for testing + * @param btDevice the corresponding Bluetooth device when relevant. * @return false if an error was reported by AudioSystem */ /*package*/ boolean handleDeviceConnection(AudioDeviceAttributes attributes, boolean connect, - boolean isForTesting) { + boolean isForTesting, @Nullable BluetoothDevice btDevice) { int device = attributes.getInternalType(); String address = attributes.getAddress(); String deviceName = attributes.getName(); @@ -889,6 +1187,7 @@ public class AudioDeviceInventory { .set(MediaMetrics.Property.MODE, connect ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT) .set(MediaMetrics.Property.NAME, deviceName); + boolean status = false; synchronized (mDevicesLock) { final String deviceKey = DeviceInfo.makeDeviceListKey(device, address); if (AudioService.DEBUG_DEVICES) { @@ -916,24 +1215,33 @@ public class AudioDeviceInventory { .record(); return false; } - mConnectedDevices.put(deviceKey, new DeviceInfo( - device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + mConnectedDevices.put(deviceKey, new DeviceInfo(device, deviceName, address)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); - mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); - return true; + status = true; } else if (!connect && isConnected) { mAudioSystem.setDeviceConnectionState(attributes, AudioSystem.DEVICE_STATE_UNAVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); // always remove even if disconnection failed mConnectedDevices.remove(deviceKey); + status = true; + } + if (status) { + if (AudioSystem.isBluetoothScoDevice(device)) { + updateBluetoothPreferredModes_l(); + if (connect) { + mDeviceBroker.postNotifyPreferredAudioProfileApplied(btDevice); + } else { + purgeDevicesRoles_l(); + } + } mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record(); - return true; + } else { + Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey + + ", deviceSpec=" + di + ", connect=" + connect); + mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record(); } - Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey - + ", deviceSpec=" + di + ", connect=" + connect); } - mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record(); - return false; + return status; } @@ -1142,15 +1450,20 @@ public class AudioDeviceInventory { // Internal utilities @GuardedBy("mDevicesLock") - private void makeA2dpDeviceAvailable(String address, String name, String eventSource, - int a2dpCodec) { + private void makeA2dpDeviceAvailable(AudioDeviceBroker.BtDeviceInfo btInfo, + String eventSource) { + final String address = btInfo.mDevice.getAddress(); + final String name = BtHelper.getName(btInfo.mDevice); + final int a2dpCodec = btInfo.mCodec; + // enable A2DP before notifying A2DP connection to avoid unnecessary processing in // audio policy manager mDeviceBroker.setBluetoothA2dpOnInt(true, true /*fromA2dp*/, eventSource); // at this point there could be another A2DP device already connected in APM, but it // doesn't matter as this new one will overwrite the previous one - final int res = mAudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name), + AudioDeviceAttributes ada = new AudioDeviceAttributes( + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address, name); + final int res = mAudioSystem.setDeviceConnectionState(ada, AudioSystem.DEVICE_STATE_AVAILABLE, a2dpCodec); // TODO: log in MediaMetrics once distinction between connection failure and @@ -1167,13 +1480,12 @@ public class AudioDeviceInventory { } // Reset A2DP suspend state each time a new sink is connected - mAudioSystem.setParameters("A2dpSuspended=false"); + mDeviceBroker.clearA2dpSuspended(); // The convention for head tracking sensors associated with A2DP devices is to // use a UUID derived from the MAC address as follows: // time_low = 0, time_mid = 0, time_hi = 0, clock_seq = 0, node = MAC Address - UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes( - new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); + UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada); final DeviceInfo di = new DeviceInfo(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, name, address, a2dpCodec, sensorUuid); final String diKey = di.getKey(); @@ -1184,6 +1496,206 @@ public class AudioDeviceInventory { mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP); setCurrentAudioRouteNameIfPossible(name, true /*fromA2dp*/); + + updateBluetoothPreferredModes_l(); + mDeviceBroker.postNotifyPreferredAudioProfileApplied(btInfo.mDevice); + } + + static final int[] CAPTURE_PRESETS = new int[] {AudioSource.MIC, AudioSource.CAMCORDER, + AudioSource.VOICE_RECOGNITION, AudioSource.VOICE_COMMUNICATION, + AudioSource.UNPROCESSED, AudioSource.VOICE_PERFORMANCE, AudioSource.HOTWORD}; + + // reflects system property persist.bluetooth.enable_dual_mode_audio + final boolean mBluetoothDualModeEnabled; + /** + * Goes over all connected Bluetooth devices and set the audio policy device role to DISABLED + * or not according to their own and other devices modes. + * The top priority is given to LE devices, then SCO ,then A2DP. + */ + @GuardedBy("mDevicesLock") + private void applyConnectedDevicesRoles_l() { + if (!mBluetoothDualModeEnabled) { + return; + } + DeviceInfo leOutDevice = + getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_BLE_SET); + DeviceInfo leInDevice = + getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_BLE_SET); + DeviceInfo a2dpDevice = + getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_A2DP_SET); + DeviceInfo scoOutDevice = + getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_OUT_ALL_SCO_SET); + DeviceInfo scoInDevice = + getFirstConnectedDeviceOfTypes(AudioSystem.DEVICE_IN_ALL_SCO_SET); + boolean disableA2dp = (leOutDevice != null && leOutDevice.isOutputOnlyModeEnabled()); + boolean disableSco = (leOutDevice != null && leOutDevice.isDuplexModeEnabled()) + || (leInDevice != null && leInDevice.isDuplexModeEnabled()); + AudioDeviceAttributes communicationDevice = + mDeviceBroker.mActiveCommunicationDevice == null + ? null : ((mDeviceBroker.isInCommunication() + && mDeviceBroker.mActiveCommunicationDevice != null) + ? new AudioDeviceAttributes(mDeviceBroker.mActiveCommunicationDevice) + : null); + + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, "applyConnectedDevicesRoles_l\n - leOutDevice: " + leOutDevice + + "\n - leInDevice: " + leInDevice + + "\n - a2dpDevice: " + a2dpDevice + + "\n - scoOutDevice: " + scoOutDevice + + "\n - scoInDevice: " + scoInDevice + + "\n - disableA2dp: " + disableA2dp + + ", disableSco: " + disableSco); + } + + for (DeviceInfo di : mConnectedDevices.values()) { + if (!AudioSystem.isBluetoothDevice(di.mDeviceType)) { + continue; + } + AudioDeviceAttributes ada = + new AudioDeviceAttributes(di.mDeviceType, di.mDeviceAddress, di.mDeviceName); + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, " + checking Device: " + ada); + } + if (ada.equalTypeAddress(communicationDevice)) { + continue; + } + + if (AudioSystem.isBluetoothOutDevice(di.mDeviceType)) { + for (AudioProductStrategy strategy : mStrategies) { + boolean disable = false; + if (strategy.getId() == mDeviceBroker.mCommunicationStrategyId) { + if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) { + disable = disableSco || !di.isDuplexModeEnabled(); + } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) { + disable = !di.isDuplexModeEnabled(); + } + } else { + if (AudioSystem.isBluetoothA2dpOutDevice(di.mDeviceType)) { + disable = disableA2dp || !di.isOutputOnlyModeEnabled(); + } else if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) { + disable = disableSco || !di.isOutputOnlyModeEnabled(); + } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) { + disable = !di.isOutputOnlyModeEnabled(); + } + } + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, " - strategy: " + strategy.getId() + + ", disable: " + disable); + } + if (disable) { + addDevicesRoleForStrategy(strategy.getId(), + AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada)); + } else { + removeDevicesRoleForStrategy(strategy.getId(), + AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada)); + } + } + } + if (AudioSystem.isBluetoothInDevice(di.mDeviceType)) { + for (int capturePreset : CAPTURE_PRESETS) { + boolean disable = false; + if (AudioSystem.isBluetoothScoDevice(di.mDeviceType)) { + disable = disableSco || !di.isDuplexModeEnabled(); + } else if (AudioSystem.isBluetoothLeDevice(di.mDeviceType)) { + disable = !di.isDuplexModeEnabled(); + } + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, " - capturePreset: " + capturePreset + + ", disable: " + disable); + } + if (disable) { + addDevicesRoleForCapturePreset(capturePreset, + AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada)); + } else { + removeDevicesRoleForCapturePreset(capturePreset, + AudioSystem.DEVICE_ROLE_DISABLED, Arrays.asList(ada)); + } + } + } + } + } + + /* package */ void applyConnectedDevicesRoles() { + synchronized (mDevicesLock) { + applyConnectedDevicesRoles_l(); + } + } + + @GuardedBy("mDevicesLock") + int checkProfileIsConnected(int profile) { + switch (profile) { + case BluetoothProfile.HEADSET: + if (getFirstConnectedDeviceOfTypes( + AudioSystem.DEVICE_OUT_ALL_SCO_SET) != null + || getFirstConnectedDeviceOfTypes( + AudioSystem.DEVICE_IN_ALL_SCO_SET) != null) { + return profile; + } + break; + case BluetoothProfile.A2DP: + if (getFirstConnectedDeviceOfTypes( + AudioSystem.DEVICE_OUT_ALL_A2DP_SET) != null) { + return profile; + } + break; + case BluetoothProfile.LE_AUDIO: + case BluetoothProfile.LE_AUDIO_BROADCAST: + if (getFirstConnectedDeviceOfTypes( + AudioSystem.DEVICE_OUT_ALL_BLE_SET) != null + || getFirstConnectedDeviceOfTypes( + AudioSystem.DEVICE_IN_ALL_BLE_SET) != null) { + return profile; + } + break; + default: + break; + } + return 0; + } + + @GuardedBy("mDevicesLock") + private void updateBluetoothPreferredModes_l() { + if (!mBluetoothDualModeEnabled) { + return; + } + HashSet<String> processedAddresses = new HashSet<>(0); + for (DeviceInfo di : mConnectedDevices.values()) { + if (!AudioSystem.isBluetoothDevice(di.mDeviceType) + || processedAddresses.contains(di.mDeviceAddress)) { + continue; + } + Bundle preferredProfiles = BtHelper.getPreferredAudioProfiles(di.mDeviceAddress); + if (AudioService.DEBUG_DEVICES) { + Log.i(TAG, "updateBluetoothPreferredModes_l processing device address: " + + di.mDeviceAddress + ", preferredProfiles: " + preferredProfiles); + } + for (DeviceInfo di2 : mConnectedDevices.values()) { + if (!AudioSystem.isBluetoothDevice(di2.mDeviceType) + || !di.mDeviceAddress.equals(di2.mDeviceAddress)) { + continue; + } + int profile = BtHelper.getProfileFromType(di2.mDeviceType); + if (profile == 0) { + continue; + } + int preferredProfile = checkProfileIsConnected( + preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_DUPLEX)); + if (preferredProfile == profile || preferredProfile == 0) { + di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_DUPLEX); + } else { + di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_DUPLEX); + } + preferredProfile = checkProfileIsConnected( + preferredProfiles.getInt(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY)); + if (preferredProfile == profile || preferredProfile == 0) { + di2.setModeEnabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY); + } else { + di2.setModeDisabled(BluetoothAdapter.AUDIO_MODE_OUTPUT_ONLY); + } + } + processedAddresses.add(di.mDeviceAddress); + } + applyConnectedDevicesRoles_l(); } @GuardedBy("mDevicesLock") @@ -1231,13 +1743,17 @@ public class AudioDeviceInventory { // Remove A2DP routes as well setCurrentAudioRouteNameIfPossible(null, true /*fromA2dp*/); mmi.record(); + + updateBluetoothPreferredModes_l(); + purgeDevicesRoles_l(); } @GuardedBy("mDevicesLock") private void makeA2dpDeviceUnavailableLater(String address, int delayMs) { // prevent any activity on the A2DP audio output to avoid unwanted // reconnection of the sink. - mAudioSystem.setParameters("A2dpSuspended=true"); + mDeviceBroker.setA2dpSuspended( + true /*enable*/, true /*internal*/, "makeA2dpDeviceUnavailableLater"); // retrieve DeviceInfo before removing device final String deviceKey = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address); @@ -1259,8 +1775,7 @@ public class AudioDeviceInventory { AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put( DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, address), - new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", - address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + new DeviceInfo(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, "", address)); } @GuardedBy("mDevicesLock") @@ -1286,8 +1801,7 @@ public class AudioDeviceInventory { AudioSystem.AUDIO_FORMAT_DEFAULT); mConnectedDevices.put( DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address), - new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, - address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + new DeviceInfo(AudioSystem.DEVICE_OUT_HEARING_AID, name, address)); mDeviceBroker.postAccessoryPlugMediaUnmute(AudioSystem.DEVICE_OUT_HEARING_AID); mDeviceBroker.postApplyVolumeOnDevice(streamType, AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable"); @@ -1325,29 +1839,56 @@ public class AudioDeviceInventory { * @return true if a DEVICE_OUT_HEARING_AID is connected, false otherwise. */ boolean isHearingAidConnected() { + return getFirstConnectedDeviceOfTypes( + Sets.newHashSet(AudioSystem.DEVICE_OUT_HEARING_AID)) != null; + } + + /** + * Returns a DeviceInfo for the fist connected device matching one of the supplied types + */ + private DeviceInfo getFirstConnectedDeviceOfTypes(Set<Integer> internalTypes) { + List<DeviceInfo> devices = getConnectedDevicesOfTypes(internalTypes); + return devices.isEmpty() ? null : devices.get(0); + } + + /** + * Returns a list of connected devices matching one one of the supplied types + */ + private List<DeviceInfo> getConnectedDevicesOfTypes(Set<Integer> internalTypes) { + ArrayList<DeviceInfo> devices = new ArrayList<>(); synchronized (mDevicesLock) { for (DeviceInfo di : mConnectedDevices.values()) { - if (di.mDeviceType == AudioSystem.DEVICE_OUT_HEARING_AID) { - return true; + if (internalTypes.contains(di.mDeviceType)) { + devices.add(di); } } - return false; } + return devices; + } + + /* package */ AudioDeviceAttributes getDeviceOfType(int type) { + DeviceInfo di = getFirstConnectedDeviceOfTypes(Sets.newHashSet(type)); + return di == null ? null : new AudioDeviceAttributes( + di.mDeviceType, di.mDeviceAddress, di.mDeviceName); } @GuardedBy("mDevicesLock") - private void makeLeAudioDeviceAvailable(String address, String name, int streamType, - int volumeIndex, int device, String eventSource) { + private void makeLeAudioDeviceAvailable( + AudioDeviceBroker.BtDeviceInfo btInfo, int streamType, String eventSource) { + final String address = btInfo.mDevice.getAddress(); + final String name = BtHelper.getName(btInfo.mDevice); + final int volumeIndex = btInfo.mVolume == -1 ? -1 : btInfo.mVolume * 10; + final int device = btInfo.mAudioSystemDevice; + if (device != AudioSystem.DEVICE_NONE) { /* Audio Policy sees Le Audio similar to A2DP. Let's make sure * AUDIO_POLICY_FORCE_NO_BT_A2DP is not set */ mDeviceBroker.setBluetoothA2dpOnInt(true, false /*fromA2dp*/, eventSource); - final int res = AudioSystem.setDeviceConnectionState(new AudioDeviceAttributes( - device, address, name), - AudioSystem.DEVICE_STATE_AVAILABLE, - AudioSystem.AUDIO_FORMAT_DEFAULT); + AudioDeviceAttributes ada = new AudioDeviceAttributes(device, address, name); + final int res = AudioSystem.setDeviceConnectionState(ada, + AudioSystem.DEVICE_STATE_AVAILABLE, AudioSystem.AUDIO_FORMAT_DEFAULT); if (res != AudioSystem.AUDIO_STATUS_OK) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "APM failed to make available LE Audio device addr=" + address @@ -1358,12 +1899,13 @@ public class AudioDeviceInventory { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "LE Audio device addr=" + address + " now available").printLog(TAG)); } - // Reset LEA suspend state each time a new sink is connected - mAudioSystem.setParameters("LeAudioSuspended=false"); + mDeviceBroker.clearLeAudioSuspended(); + UUID sensorUuid = UuidUtils.uuidFromAudioDeviceAttributes(ada); mConnectedDevices.put(DeviceInfo.makeDeviceListKey(device, address), - new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT)); + new DeviceInfo(device, name, address, AudioSystem.AUDIO_FORMAT_DEFAULT, + sensorUuid)); mDeviceBroker.postAccessoryPlugMediaUnmute(device); setCurrentAudioRouteNameIfPossible(name, /*fromA2dp=*/false); } @@ -1379,6 +1921,9 @@ public class AudioDeviceInventory { final int maxIndex = mDeviceBroker.getMaxVssVolumeForStream(streamType); mDeviceBroker.postSetLeAudioVolumeIndex(leAudioVolIndex, maxIndex, streamType); mDeviceBroker.postApplyVolumeOnDevice(streamType, device, "makeLeAudioDeviceAvailable"); + + updateBluetoothPreferredModes_l(); + mDeviceBroker.postNotifyPreferredAudioProfileApplied(btInfo.mDevice); } @GuardedBy("mDevicesLock") @@ -1403,13 +1948,17 @@ public class AudioDeviceInventory { } setCurrentAudioRouteNameIfPossible(null, false /*fromA2dp*/); + + updateBluetoothPreferredModes_l(); + purgeDevicesRoles_l(); } @GuardedBy("mDevicesLock") private void makeLeAudioDeviceUnavailableLater(String address, int device, int delayMs) { // prevent any activity on the LEA output to avoid unwanted // reconnection of the sink. - mAudioSystem.setParameters("LeAudioSuspended=true"); + mDeviceBroker.setLeAudioSuspended( + true /*enable*/, true /*internal*/, "makeLeAudioDeviceUnavailableLater"); // the device will be made unavailable later, so consider it disconnected right away mConnectedDevices.remove(DeviceInfo.makeDeviceListKey(device, address)); // send the delayed message to make the device unavailable later @@ -1737,18 +2286,6 @@ public class AudioDeviceInventory { } } - /* package */ AudioDeviceAttributes getDeviceOfType(int type) { - synchronized (mDevicesLock) { - for (DeviceInfo di : mConnectedDevices.values()) { - if (di.mDeviceType == type) { - return new AudioDeviceAttributes( - di.mDeviceType, di.mDeviceAddress, di.mDeviceName); - } - } - } - return null; - } - //---------------------------------------------------------- // For tests only @@ -1759,10 +2296,12 @@ public class AudioDeviceInventory { */ @VisibleForTesting public boolean isA2dpDeviceConnected(@NonNull BluetoothDevice device) { - final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, - device.getAddress()); - synchronized (mDevicesLock) { - return (mConnectedDevices.get(key) != null); + for (DeviceInfo di : getConnectedDevicesOfTypes( + Sets.newHashSet(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) { + if (di.mDeviceAddress.equals(device.getAddress())) { + return true; + } } + return false; } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index ac55f28a8ab0..a3163e010efa 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -6376,6 +6376,26 @@ public class AudioService extends IAudioService.Stub mDeviceBroker.setBluetoothScoOn(on, eventSource); } + /** @see AudioManager#setA2dpSuspended(boolean) */ + @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK) + public void setA2dpSuspended(boolean enable) { + super.setA2dpSuspended_enforcePermission(); + final String eventSource = new StringBuilder("setA2dpSuspended(").append(enable) + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).toString(); + mDeviceBroker.setA2dpSuspended(enable, false /*internal*/, eventSource); + } + + /** @see AudioManager#setA2dpSuspended(boolean) */ + @android.annotation.EnforcePermission(android.Manifest.permission.BLUETOOTH_STACK) + public void setLeAudioSuspended(boolean enable) { + super.setLeAudioSuspended_enforcePermission(); + final String eventSource = new StringBuilder("setLeAudioSuspended(").append(enable) + .append(") from u/pid:").append(Binder.getCallingUid()).append("/") + .append(Binder.getCallingPid()).toString(); + mDeviceBroker.setLeAudioSuspended(enable, false /*internal*/, eventSource); + } + /** @see AudioManager#isBluetoothScoOn() * Note that it doesn't report internal state, but state seen by apps (which may have * called setBluetoothScoOn() */ @@ -7053,13 +7073,16 @@ public class AudioService extends IAudioService.Stub return deviceSet.iterator().next(); } else { // Multiple device selection is either: + // - dock + one other device: give priority to dock in this case. // - speaker + one other device: give priority to speaker in this case. // - one A2DP device + another device: happens with duplicated output. In this case // retain the device on the A2DP output as the other must not correspond to an active // selection if not the speaker. // - HDMI-CEC system audio mode only output: give priority to available item in order. - if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER)) { + if (deviceSet.contains(AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET)) { + return AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET; + } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER)) { return AudioSystem.DEVICE_OUT_SPEAKER; } else if (deviceSet.contains(AudioSystem.DEVICE_OUT_SPEAKER_SAFE)) { // Note: DEVICE_OUT_SPEAKER_SAFE not present in getDeviceSetForStreamDirect diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java index 7af7ed5fff65..1142a8d64304 100644 --- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java +++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java @@ -435,7 +435,7 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, } /** - * Same as {@link AudioSystem#removeDevicesRoleForCapturePreset(int, int, int[], String[])} + * Same as {@link AudioSystem#removeDevicesRoleForCapturePreset(int, int, List)} * @param capturePreset * @param role * @param devicesToRemove @@ -448,6 +448,19 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, } /** + * Same as {@link AudioSystem#addDevicesRoleForCapturePreset(int, int, List)} + * @param capturePreset the capture preset to configure + * @param role the role of the devices + * @param devices the list of devices to be added as role for the given capture preset + * @return {@link #SUCCESS} if successfully add + */ + public int addDevicesRoleForCapturePreset( + int capturePreset, int role, @NonNull List<AudioDeviceAttributes> devices) { + invalidateRoutingCache(); + return AudioSystem.addDevicesRoleForCapturePreset(capturePreset, role, devices); + } + + /** * Same as {@link AudioSystem#} * @param capturePreset * @param role diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index 631d7f5a170d..e46c3cc4a285 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -33,6 +33,7 @@ import android.media.AudioManager; import android.media.AudioSystem; import android.media.BluetoothProfileConnectionInfo; import android.os.Binder; +import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; import android.text.TextUtils; @@ -150,60 +151,12 @@ public class BtHelper { } } - //---------------------------------------------------------------------- - /*package*/ static class BluetoothA2dpDeviceInfo { - private final @NonNull BluetoothDevice mBtDevice; - private final int mVolume; - private final @AudioSystem.AudioFormatNativeEnumForBtCodec int mCodec; - - BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice) { - this(btDevice, -1, AudioSystem.AUDIO_FORMAT_DEFAULT); - } - - BluetoothA2dpDeviceInfo(@NonNull BluetoothDevice btDevice, int volume, int codec) { - mBtDevice = btDevice; - mVolume = volume; - mCodec = codec; - } - - public @NonNull BluetoothDevice getBtDevice() { - return mBtDevice; - } - - public int getVolume() { - return mVolume; - } - - public @AudioSystem.AudioFormatNativeEnumForBtCodec int getCodec() { - return mCodec; - } - - // redefine equality op so we can match messages intended for this device - @Override - public boolean equals(Object o) { - if (o == null) { - return false; - } - if (this == o) { - return true; - } - if (o instanceof BluetoothA2dpDeviceInfo) { - return mBtDevice.equals(((BluetoothA2dpDeviceInfo) o).getBtDevice()); - } - return false; - } - - - } - // A2DP device events /*package*/ static final int EVENT_DEVICE_CONFIG_CHANGE = 0; - /*package*/ static final int EVENT_ACTIVE_DEVICE_CHANGE = 1; - /*package*/ static String a2dpDeviceEventToString(int event) { + /*package*/ static String deviceEventToString(int event) { switch (event) { case EVENT_DEVICE_CONFIG_CHANGE: return "DEVICE_CONFIG_CHANGE"; - case EVENT_ACTIVE_DEVICE_CHANGE: return "ACTIVE_DEVICE_CHANGE"; default: return new String("invalid event:" + event); } @@ -492,8 +445,8 @@ public class BtHelper { /*package*/ synchronized void resetBluetoothSco() { mScoAudioState = SCO_STATE_INACTIVE; broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED); - AudioSystem.setParameters("A2dpSuspended=false"); - AudioSystem.setParameters("LeAudioSuspended=false"); + mDeviceBroker.clearA2dpSuspended(); + mDeviceBroker.clearLeAudioSuspended(); mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco"); } @@ -620,11 +573,12 @@ public class BtHelper { return btHeadsetDeviceToAudioDevice(mBluetoothHeadsetDevice); } - private AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) { + private static AudioDeviceAttributes btHeadsetDeviceToAudioDevice(BluetoothDevice btDevice) { if (btDevice == null) { return new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""); } String address = btDevice.getAddress(); + String name = getName(btDevice); if (!BluetoothAdapter.checkBluetoothAddress(address)) { address = ""; } @@ -646,7 +600,7 @@ public class BtHelper { + " btClass: " + (btClass == null ? "Unknown" : btClass) + " nativeType: " + nativeType + " address: " + address); } - return new AudioDeviceAttributes(nativeType, address); + return new AudioDeviceAttributes(nativeType, address, name); } private boolean handleBtScoActiveDeviceChange(BluetoothDevice btDevice, boolean isActive) { @@ -655,12 +609,9 @@ public class BtHelper { } int inDevice = AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET; AudioDeviceAttributes audioDevice = btHeadsetDeviceToAudioDevice(btDevice); - String btDeviceName = getName(btDevice); boolean result = false; if (isActive) { - result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( - audioDevice.getInternalType(), audioDevice.getAddress(), btDeviceName), - isActive); + result |= mDeviceBroker.handleDeviceConnection(audioDevice, isActive, btDevice); } else { int[] outDeviceTypes = { AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, @@ -669,14 +620,14 @@ public class BtHelper { }; for (int outDeviceType : outDeviceTypes) { result |= mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( - outDeviceType, audioDevice.getAddress(), btDeviceName), - isActive); + outDeviceType, audioDevice.getAddress(), audioDevice.getName()), + isActive, btDevice); } } // handleDeviceConnection() && result to make sure the method get executed result = mDeviceBroker.handleDeviceConnection(new AudioDeviceAttributes( - inDevice, audioDevice.getAddress(), btDeviceName), - isActive) && result; + inDevice, audioDevice.getAddress(), audioDevice.getName()), + isActive, btDevice) && result; return result; } @@ -973,6 +924,30 @@ public class BtHelper { } } + /*package */ static int getProfileFromType(int deviceType) { + if (AudioSystem.isBluetoothA2dpOutDevice(deviceType)) { + return BluetoothProfile.A2DP; + } else if (AudioSystem.isBluetoothScoDevice(deviceType)) { + return BluetoothProfile.HEADSET; + } else if (AudioSystem.isBluetoothLeDevice(deviceType)) { + return BluetoothProfile.LE_AUDIO; + } + return 0; // 0 is not a valid profile + } + + /*package */ static Bundle getPreferredAudioProfiles(String address) { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + return adapter.getPreferredAudioProfiles(adapter.getRemoteDevice(address)); + } + + /** + * Notifies Bluetooth framework that new preferred audio profiles for Bluetooth devices + * have been applied. + */ + public static void onNotifyPreferredAudioProfileApplied(BluetoothDevice btDevice) { + BluetoothAdapter.getDefaultAdapter().notifyActiveDeviceChangeApplied(btDevice); + } + /** * Returns the string equivalent for the btDeviceClass class. */ diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java index 7ae31b2a114d..50d375c56f4a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClient.java @@ -212,6 +212,8 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut // 1) Authenticated == true // 2) Error occurred // 3) Authenticated == false + // 4) onLockout + // 5) onLockoutTimed mCallback.onClientFinished(this, true /* success */); } @@ -304,11 +306,7 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut PerformanceTracker.getInstanceForSensorId(getSensorId()) .incrementTimedLockoutForUser(getTargetUserId()); - try { - getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } + onError(error, 0 /* vendorCode */); } @Override @@ -323,10 +321,6 @@ class FaceAuthenticationClient extends AuthenticationClient<AidlSession, FaceAut PerformanceTracker.getInstanceForSensorId(getSensorId()) .incrementPermanentLockoutForUser(getTargetUserId()); - try { - getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception", e); - } + onError(error, 0 /* vendorCode */); } } diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java index 1a53fec82d98..c5037b7012f2 100644 --- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/FaceProvider.java @@ -465,7 +465,7 @@ public class FaceProvider implements IBinder.DeathRecipient, ServiceProvider { BaseClientMonitor clientMonitor, boolean success) { mAuthSessionCoordinator.authEndedFor(userId, Utils.getCurrentStrength(sensorId), - sensorId, requestId, success); + sensorId, requestId, client.wasAuthSuccessful()); } }); }); diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 7e48f68dcefc..0f17139e2678 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -969,15 +969,21 @@ public class Vpn { // Allow VpnManager app to temporarily run background services to handle this error. // If an app requires anything beyond this grace period, they MUST either declare // themselves as a foreground service, or schedule a job/workitem. - DeviceIdleInternal idleController = mDeps.getDeviceIdleInternal(); - idleController.addPowerSaveTempWhitelistApp(Process.myUid(), packageName, - VPN_MANAGER_EVENT_ALLOWLIST_DURATION_MS, mUserId, false, REASON_VPN, - "VpnManager event"); + final long token = Binder.clearCallingIdentity(); try { - return mUserIdContext.startService(intent) != null; - } catch (RuntimeException e) { - Log.e(TAG, "Service of VpnManager app " + intent + " failed to start", e); - return false; + final DeviceIdleInternal idleController = mDeps.getDeviceIdleInternal(); + idleController.addPowerSaveTempWhitelistApp(Process.myUid(), packageName, + VPN_MANAGER_EVENT_ALLOWLIST_DURATION_MS, mUserId, false, REASON_VPN, + "VpnManager event"); + + try { + return mUserIdContext.startService(intent) != null; + } catch (RuntimeException e) { + Log.e(TAG, "Service of VpnManager app " + intent + " failed to start", e); + return false; + } + } finally { + Binder.restoreCallingIdentity(token); } } diff --git a/services/core/java/com/android/server/display/PersistentDataStore.java b/services/core/java/com/android/server/display/PersistentDataStore.java index ec70c8966736..6d6ed726161b 100644 --- a/services/core/java/com/android/server/display/PersistentDataStore.java +++ b/services/core/java/com/android/server/display/PersistentDataStore.java @@ -306,8 +306,11 @@ final class PersistentDataStore { } public boolean setBrightness(DisplayDevice displayDevice, float brightness) { + if (displayDevice == null || !displayDevice.hasStableUniqueId()) { + return false; + } final String displayDeviceUniqueId = displayDevice.getUniqueId(); - if (!displayDevice.hasStableUniqueId() || displayDeviceUniqueId == null) { + if (displayDeviceUniqueId == null) { return false; } final DisplayState state = getDisplayState(displayDeviceUniqueId, true); diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java index 7802b9d24de9..0e26d4661017 100644 --- a/services/core/java/com/android/server/dreams/DreamManagerService.java +++ b/services/core/java/com/android/server/dreams/DreamManagerService.java @@ -124,7 +124,7 @@ public final class DreamManagerService extends SystemService { private final boolean mDreamsEnabledByDefaultConfig; private final boolean mDreamsActivatedOnChargeByDefault; private final boolean mDreamsActivatedOnDockByDefault; - private final boolean mKeepDreamingWhenUndockedDefault; + private final boolean mKeepDreamingWhenUnpluggingDefault; private final CopyOnWriteArrayList<DreamManagerInternal.DreamManagerStateListener> mDreamManagerStateListeners = new CopyOnWriteArrayList<>(); @@ -236,8 +236,8 @@ public final class DreamManagerService extends SystemService { mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean( com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault); mSettingsObserver = new SettingsObserver(mHandler); - mKeepDreamingWhenUndockedDefault = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_keepDreamingWhenUndocking); + mKeepDreamingWhenUnpluggingDefault = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_keepDreamingWhenUnplugging); } @Override @@ -311,7 +311,7 @@ public final class DreamManagerService extends SystemService { pw.println("mIsDocked=" + mIsDocked); pw.println("mIsCharging=" + mIsCharging); pw.println("mWhenToDream=" + mWhenToDream); - pw.println("mKeepDreamingWhenUndockedDefault=" + mKeepDreamingWhenUndockedDefault); + pw.println("mKeepDreamingWhenUnpluggingDefault=" + mKeepDreamingWhenUnpluggingDefault); pw.println("getDozeComponent()=" + getDozeComponent()); pw.println(); @@ -340,11 +340,11 @@ public final class DreamManagerService extends SystemService { } } - private void reportKeepDreamingWhenUndockedChanged(boolean keepDreaming) { + private void reportKeepDreamingWhenUnpluggingChanged(boolean keepDreaming) { mHandler.post(() -> { for (DreamManagerInternal.DreamManagerStateListener listener : mDreamManagerStateListeners) { - listener.onKeepDreamingWhenUndockedChanged(keepDreaming); + listener.onKeepDreamingWhenUnpluggingChanged(keepDreaming); } }); } @@ -600,8 +600,7 @@ public final class DreamManagerService extends SystemService { } mSystemDreamComponent = componentName; - reportKeepDreamingWhenUndockedChanged(shouldKeepDreamingWhenUndocked()); - + reportKeepDreamingWhenUnpluggingChanged(shouldKeepDreamingWhenUnplugging()); // Switch dream if currently dreaming and not dozing. if (isDreamingInternal() && !isDozingInternal()) { startDreamInternal(false /*doze*/, (mSystemDreamComponent == null ? "clear" : "set") @@ -610,8 +609,8 @@ public final class DreamManagerService extends SystemService { } } - private boolean shouldKeepDreamingWhenUndocked() { - return mKeepDreamingWhenUndockedDefault && mSystemDreamComponent == null; + private boolean shouldKeepDreamingWhenUnplugging() { + return mKeepDreamingWhenUnpluggingDefault && mSystemDreamComponent == null; } private ComponentName getDefaultDreamComponentForUser(int userId) { @@ -1057,7 +1056,7 @@ public final class DreamManagerService extends SystemService { public void registerDreamManagerStateListener(DreamManagerStateListener listener) { mDreamManagerStateListeners.add(listener); // Initialize the listener's state. - listener.onKeepDreamingWhenUndockedChanged(shouldKeepDreamingWhenUndocked()); + listener.onKeepDreamingWhenUnpluggingChanged(shouldKeepDreamingWhenUnplugging()); } @Override diff --git a/services/core/java/com/android/server/inputmethod/OWNERS b/services/core/java/com/android/server/inputmethod/OWNERS index 00cd700541c0..6e5eb5631112 100644 --- a/services/core/java/com/android/server/inputmethod/OWNERS +++ b/services/core/java/com/android/server/inputmethod/OWNERS @@ -1,8 +1,8 @@ set noparent -ogunwale@google.com +roosa@google.com yukawa@google.com tarandeep@google.com -lumark@google.com -roosa@google.com -wilsonwu@google.com + +ogunwale@google.com #{LAST_RESORT_SUGGESTION} +jjaggi@google.com #{LAST_RESORT_SUGGESTION} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 1301cd476c26..ebcbfed93ac7 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -3335,7 +3335,7 @@ public class NotificationManagerService extends SystemService { } checkCallerIsSameApp(pkg); - final boolean isSystemToast = isCallerSystemOrPhone() + final boolean isSystemToast = isCallerIsSystemOrSystemUi() || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg); boolean isAppRenderedToast = (callback != null); if (!checkCanEnqueueToast(pkg, callingUid, displayId, isAppRenderedToast, diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 69ef3f780172..f0ba8727e4c7 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -994,7 +994,7 @@ final class InstallPackageHelper { reconciledPackages = ReconcilePackageUtils.reconcilePackages( requests, Collections.unmodifiableMap(mPm.mPackages), versionInfos, mSharedLibraries, mPm.mSettings.getKeySetManagerService(), - mPm.mSettings, mContext); + mPm.mSettings); } catch (ReconcileFailure e) { for (InstallRequest request : requests) { request.setError("Reconciliation failed...", e); @@ -1135,22 +1135,22 @@ final class InstallPackageHelper { // behavior. if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, "MinInstallableTargetSdk__install_block_enabled", - false)) { + true)) { int minInstallableTargetSdk = DeviceConfig.getInt(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, "MinInstallableTargetSdk__min_installable_target_sdk", - 0); + PackageManagerService.MIN_INSTALLABLE_TARGET_SDK); // Determine if enforcement is in strict mode boolean strictMode = false; if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, "MinInstallableTargetSdk__install_block_strict_mode_enabled", - false)) { + true)) { if (parsedPackage.getTargetSdkVersion() < DeviceConfig.getInt(DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, "MinInstallableTargetSdk__strict_mode_target_sdk", - 0)) { + PackageManagerService.MIN_INSTALLABLE_TARGET_SDK)) { strictMode = true; } } @@ -3930,7 +3930,7 @@ final class InstallPackageHelper { mPm.mPackages, Collections.singletonMap(pkgName, mPm.getSettingsVersionForPackage(parsedPackage)), mSharedLibraries, mPm.mSettings.getKeySetManagerService(), - mPm.mSettings, mContext); + mPm.mSettings); if ((scanFlags & SCAN_AS_APEX) == 0) { appIdCreated = optimisticallyRegisterAppId(installRequest); } else { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index e4e3a9d0b7d3..3e1a1ac2605f 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -560,6 +560,14 @@ public class PackageManagerService implements PackageSender, TestUtilityService // How many required verifiers can be on the system. private static final int REQUIRED_VERIFIERS_MAX_COUNT = 2; + /** + * Specifies the minimum target SDK version an apk must specify in order to be installed + * on the system. This improves security and privacy by blocking low + * target sdk apps as malware can target older sdk versions to avoid + * the enforcement of new API behavior. + */ + public static final int MIN_INSTALLABLE_TARGET_SDK = Build.VERSION_CODES.M; + // Compilation reasons. // TODO(b/260124949): Clean this up with the legacy dexopt code. public static final int REASON_FIRST_BOOT = 0; diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index e3c97e933ad1..5312ae6ca84c 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -16,7 +16,6 @@ package com.android.server.pm; -import static android.content.pm.PackageManager.INSTALL_FAILED_INVALID_APK; import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE; import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES; import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY; @@ -24,24 +23,19 @@ import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRI import static com.android.server.pm.PackageManagerService.SCAN_BOOTING; import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP; -import android.content.Context; import android.content.pm.PackageManager; -import android.content.pm.PermissionInfo; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningDetails; import android.os.SystemProperties; -import android.permission.PermissionManager; import android.util.ArrayMap; import android.util.Log; import com.android.server.pm.parsing.pkg.ParsedPackage; import com.android.server.pm.pkg.AndroidPackage; -import com.android.server.pm.pkg.component.ParsedUsesPermission; import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import com.android.server.utils.WatchedLongSparseArray; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Map; @@ -60,7 +54,7 @@ final class ReconcilePackageUtils { Map<String, AndroidPackage> allPackages, Map<String, Settings.VersionInfo> versionInfos, SharedLibrariesImpl sharedLibraries, - KeySetManagerService ksms, Settings settings, Context context) + KeySetManagerService ksms, Settings settings) throws ReconcileFailure { final List<ReconciledPackage> result = new ArrayList<>(installRequests.size()); @@ -149,11 +143,11 @@ final class ReconcilePackageUtils { } else { if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, - "Package " + installPackageName + "Package " + parsedPackage.getPackageName() + " upgrade keys do not match the previously installed" + " version"); } else { - String msg = "System package " + installPackageName + String msg = "System package " + parsedPackage.getPackageName() + " signature changed; retaining data."; PackageManagerService.reportSettingsProblem(Log.WARN, msg); } @@ -174,42 +168,11 @@ final class ReconcilePackageUtils { removeAppKeySetData = true; } - // if this is a sharedUser, check to see if the new package is signed by a - // newer signing certificate than the existing one, and if so, copy over the new + // if this is is a sharedUser, check to see if the new package is signed by a + // newer + // signing certificate than the existing one, and if so, copy over the new // details if (sharedUserSetting != null) { - if (!parsedPackage.isTestOnly() && sharedUserSetting.isPrivileged() - && !signatureCheckPs.isSystem()) { - final List<ParsedUsesPermission> usesPermissions = - parsedPackage.getUsesPermissions(); - final List<String> usesPrivilegedPermissions = new ArrayList<>(); - final PermissionManager permissionManager = context.getSystemService( - PermissionManager.class); - // Check if the app requests any privileged permissions because that - // violates the privapp-permissions allowlist check during boot. - if (permissionManager != null) { - for (int i = 0; i < usesPermissions.size(); i++) { - final String permissionName = usesPermissions.get(i).getName(); - final PermissionInfo permissionInfo = - permissionManager.getPermissionInfo(permissionName, 0); - if (permissionInfo != null - && (permissionInfo.getProtectionFlags() - & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0) { - usesPrivilegedPermissions.add(permissionName); - } - } - } - - if (!usesPrivilegedPermissions.isEmpty()) { - throw new ReconcileFailure(INSTALL_FAILED_INVALID_APK, - "Non-system package: " + installPackageName - + " shares signature and sharedUserId with" - + " a privileged package but requests" - + " privileged permissions that are not" - + " allowed: " + Arrays.toString( - usesPrivilegedPermissions.toArray())); - } - } // Attempt to merge the existing lineage for the shared SigningDetails with // the lineage of the new package; if the shared SigningDetails are not // returned this indicates the new package added new signers to the lineage @@ -226,7 +189,7 @@ final class ReconcilePackageUtils { for (AndroidPackage androidPackage : sharedUserSetting.getPackages()) { if (androidPackage.getPackageName() != null && !androidPackage.getPackageName().equals( - installPackageName)) { + parsedPackage.getPackageName())) { mergedDetails = mergedDetails.mergeLineageWith( androidPackage.getSigningDetails(), MERGE_RESTRICTED_CAPABILITY); @@ -256,7 +219,7 @@ final class ReconcilePackageUtils { if (sharedUserSetting != null) { if (sharedUserSetting.signaturesChanged != null && !PackageManagerServiceUtils.canJoinSharedUserId( - installPackageName, parsedPackage.getSigningDetails(), + parsedPackage.getPackageName(), parsedPackage.getSigningDetails(), sharedUserSetting, PackageManagerServiceUtils.SHARED_USER_ID_JOIN_TYPE_SYSTEM)) { if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) { @@ -277,7 +240,7 @@ final class ReconcilePackageUtils { // whichever package happened to be scanned later. throw new IllegalStateException( "Signature mismatch on system package " - + installPackageName + + parsedPackage.getPackageName() + " for shared user " + sharedUserSetting); } @@ -289,7 +252,7 @@ final class ReconcilePackageUtils { sharedUserSetting.signaturesChanged = Boolean.TRUE; } // File a report about this. - String msg = "System package " + installPackageName + String msg = "System package " + parsedPackage.getPackageName() + " signature changed; retaining data."; PackageManagerService.reportSettingsProblem(Log.WARN, msg); } catch (IllegalArgumentException e) { diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 73be5490e0e1..5f8efe29459d 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -2641,6 +2641,7 @@ public class UserManagerService extends IUserManager.Stub { private void setUserRestrictionInner(int userId, @NonNull String key, boolean value) { if (!UserRestrictionsUtils.isValidRestriction(key)) { + Slog.e(LOG_TAG, "Setting invalid restriction " + key); return; } synchronized (mRestrictionsLock) { diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 2e8a150f2b6d..e392c24026a7 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -667,15 +667,15 @@ public final class PowerManagerService extends SystemService // but the DreamService has not yet been told to start (it's an async process). private boolean mDozeStartInProgress; - // Whether to keep dreaming when the device is undocked. - private boolean mKeepDreamingWhenUndocked; + // Whether to keep dreaming when the device is unplugging. + private boolean mKeepDreamingWhenUnplugging; private final class DreamManagerStateListener implements DreamManagerInternal.DreamManagerStateListener { @Override - public void onKeepDreamingWhenUndockedChanged(boolean keepDreaming) { + public void onKeepDreamingWhenUnpluggingChanged(boolean keepDreaming) { synchronized (mLock) { - mKeepDreamingWhenUndocked = keepDreaming; + mKeepDreamingWhenUnplugging = keepDreaming; } } } @@ -2504,14 +2504,12 @@ public final class PowerManagerService extends SystemService return false; } - // Don't wake when undocking while dreaming if configured not to. - if (mKeepDreamingWhenUndocked + // Don't wake when unplugging while dreaming if configured not to. + if (mKeepDreamingWhenUnplugging && getGlobalWakefulnessLocked() == WAKEFULNESS_DREAMING - && wasPowered && !mIsPowered - && oldPlugType == BatteryManager.BATTERY_PLUGGED_DOCK) { + && wasPowered && !mIsPowered) { return false; } - // Don't wake when undocked from wireless charger. // See WirelessChargerDetector for justification. if (wasPowered && !mIsPowered @@ -4477,7 +4475,7 @@ public final class PowerManagerService extends SystemService + mWakeUpWhenPluggedOrUnpluggedInTheaterModeConfig); pw.println(" mTheaterModeEnabled=" + mTheaterModeEnabled); - pw.println(" mKeepDreamingWhenUndocked=" + mKeepDreamingWhenUndocked); + pw.println(" mKeepDreamingWhenUnplugging=" + mKeepDreamingWhenUnplugging); pw.println(" mSuspendWhenScreenOffDueToProximityConfig=" + mSuspendWhenScreenOffDueToProximityConfig); pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig); diff --git a/services/core/java/com/android/server/power/ShutdownCheckPoints.java b/services/core/java/com/android/server/power/ShutdownCheckPoints.java index 32f1bcfaad76..546dc819b4e6 100644 --- a/services/core/java/com/android/server/power/ShutdownCheckPoints.java +++ b/services/core/java/com/android/server/power/ShutdownCheckPoints.java @@ -295,11 +295,18 @@ public final class ShutdownCheckPoints { @Nullable String getProcessName() { try { - List<ActivityManager.RunningAppProcessInfo> runningProcesses = - mActivityManager.getRunningAppProcesses(); - for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { - if (processInfo.pid == mCallerProcessId) { - return processInfo.processName; + List<ActivityManager.RunningAppProcessInfo> runningProcesses = null; + if (mActivityManager != null) { + runningProcesses = mActivityManager.getRunningAppProcesses(); + } else { + Slog.v(TAG, "No ActivityManager available to find process name with pid=" + + mCallerProcessId); + } + if (runningProcesses != null) { + for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { + if (processInfo.pid == mCallerProcessId) { + return processInfo.processName; + } } } } catch (RemoteException e) { diff --git a/services/core/java/com/android/server/power/ShutdownThread.java b/services/core/java/com/android/server/power/ShutdownThread.java index c2d4ac694c39..b1430e7138e7 100644 --- a/services/core/java/com/android/server/power/ShutdownThread.java +++ b/services/core/java/com/android/server/power/ShutdownThread.java @@ -703,17 +703,20 @@ public final class ShutdownThread extends Thread { // vibrate before shutting down Vibrator vibrator = new SystemVibrator(context); try { - vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES); + if (vibrator.hasVibrator()) { + vibrator.vibrate(SHUTDOWN_VIBRATE_MS, VIBRATION_ATTRIBUTES); + // vibrator is asynchronous so we need to wait to avoid shutting down too soon. + try { + Thread.sleep(SHUTDOWN_VIBRATE_MS); + } catch (InterruptedException unused) { + // this is not critical and does not require logging + } + } } catch (Exception e) { // Failure to vibrate shouldn't interrupt shutdown. Just log it. Log.w(TAG, "Failed to vibrate during shutdown.", e); } - // vibrator is asynchronous so we need to wait to avoid shutting down too soon. - try { - Thread.sleep(SHUTDOWN_VIBRATE_MS); - } catch (InterruptedException unused) { - } } // Shutdown power Log.i(TAG, "Performing low-level shutdown..."); diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java index 231ffc6464c1..1d63489f3c4f 100644 --- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java +++ b/services/core/java/com/android/server/power/stats/wakeups/CpuWakeupStats.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package com.android.server.power.stats; +package com.android.server.power.stats.wakeups; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM; +import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI; @@ -55,7 +56,8 @@ public class CpuWakeupStats { private static final String TAG = "CpuWakeupStats"; private static final String SUBSYSTEM_ALARM_STRING = "Alarm"; - private static final String SUBSYSTEM_ALARM_WIFI = "Wifi"; + private static final String SUBSYSTEM_WIFI_STRING = "Wifi"; + private static final String SUBSYSTEM_SOUND_TRIGGER_STRING = "Sound_trigger"; private static final String TRACE_TRACK_WAKEUP_ATTRIBUTION = "wakeup_attribution"; @VisibleForTesting static final long WAKEUP_REASON_HALF_WINDOW_MS = 500; @@ -91,12 +93,24 @@ public class CpuWakeupStats { mConfig.register(new HandlerExecutor(mHandler)); } + private static int typeToStatsType(int wakeupType) { + switch (wakeupType) { + case Wakeup.TYPE_ABNORMAL: + return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_ABNORMAL; + case Wakeup.TYPE_IRQ: + return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_IRQ; + } + return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_UNKNOWN; + } + private static int subsystemToStatsReason(int subsystem) { switch (subsystem) { case CPU_WAKEUP_SUBSYSTEM_ALARM: return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__ALARM; case CPU_WAKEUP_SUBSYSTEM_WIFI: return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__WIFI; + case CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER: + return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__SOUND_TRIGGER; } return FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__REASON__UNKNOWN; } @@ -144,7 +158,7 @@ public class CpuWakeupStats { } } FrameworkStatsLog.write(FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED, - FrameworkStatsLog.KERNEL_WAKEUP_ATTRIBUTED__TYPE__TYPE_IRQ, + typeToStatsType(wakeupToLog.mType), subsystemToStatsReason(subsystem), uids, wakeupToLog.mElapsedMillis, @@ -524,8 +538,10 @@ public class CpuWakeupStats { switch (rawSubsystem) { case SUBSYSTEM_ALARM_STRING: return CPU_WAKEUP_SUBSYSTEM_ALARM; - case SUBSYSTEM_ALARM_WIFI: + case SUBSYSTEM_WIFI_STRING: return CPU_WAKEUP_SUBSYSTEM_WIFI; + case SUBSYSTEM_SOUND_TRIGGER_STRING: + return CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER; } return CPU_WAKEUP_SUBSYSTEM_UNKNOWN; } @@ -535,25 +551,43 @@ public class CpuWakeupStats { case CPU_WAKEUP_SUBSYSTEM_ALARM: return SUBSYSTEM_ALARM_STRING; case CPU_WAKEUP_SUBSYSTEM_WIFI: - return SUBSYSTEM_ALARM_WIFI; + return SUBSYSTEM_WIFI_STRING; + case CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER: + return SUBSYSTEM_SOUND_TRIGGER_STRING; case CPU_WAKEUP_SUBSYSTEM_UNKNOWN: return "Unknown"; } return "N/A"; } - private static final class Wakeup { + @VisibleForTesting + static final class Wakeup { private static final String PARSER_TAG = "CpuWakeupStats.Wakeup"; private static final String ABORT_REASON_PREFIX = "Abort"; - private static final Pattern sIrqPattern = Pattern.compile("^(\\d+)\\s+(\\S+)"); + private static final Pattern sIrqPattern = Pattern.compile("^(\\-?\\d+)\\s+(\\S+)"); + + /** + * Classical interrupts, which arrive on a dedicated GPIO pin into the main CPU. + * Sometimes, when multiple IRQs happen close to each other, they may get batched together. + */ + static final int TYPE_IRQ = 1; + + /** + * Non-IRQ wakeups. The exact mechanism for these is unknown, except that these explicitly + * do not use an interrupt line or a GPIO pin. + */ + static final int TYPE_ABNORMAL = 2; + + int mType; long mElapsedMillis; long mUptimeMillis; IrqDevice[] mDevices; - private Wakeup(IrqDevice[] devices, long elapsedMillis, long uptimeMillis) { + private Wakeup(int type, IrqDevice[] devices, long elapsedMillis, long uptimeMillis) { + mType = type; + mDevices = devices; mElapsedMillis = elapsedMillis; mUptimeMillis = uptimeMillis; - mDevices = devices; } static Wakeup parseWakeup(String rawReason, long elapsedMillis, long uptimeMillis) { @@ -563,6 +597,7 @@ public class CpuWakeupStats { return null; } + int type = TYPE_IRQ; int parsedDeviceCount = 0; final IrqDevice[] parsedDevices = new IrqDevice[components.length]; @@ -574,6 +609,10 @@ public class CpuWakeupStats { try { line = Integer.parseInt(matcher.group(1)); device = matcher.group(2); + if (line < 0) { + // Assuming that IRQ wakeups cannot come batched with non-IRQ wakeups. + type = TYPE_ABNORMAL; + } } catch (NumberFormatException e) { Slog.e(PARSER_TAG, "Exception while parsing device names from part: " + component, e); @@ -585,15 +624,16 @@ public class CpuWakeupStats { if (parsedDeviceCount == 0) { return null; } - return new Wakeup(Arrays.copyOf(parsedDevices, parsedDeviceCount), elapsedMillis, + return new Wakeup(type, Arrays.copyOf(parsedDevices, parsedDeviceCount), elapsedMillis, uptimeMillis); } @Override public String toString() { return "Wakeup{" - + "mElapsedMillis=" + mElapsedMillis - + ", mUptimeMillis=" + TimeUtils.formatDuration(mUptimeMillis) + + "mType=" + mType + + ", mElapsedMillis=" + mElapsedMillis + + ", mUptimeMillis=" + mUptimeMillis + ", mDevices=" + Arrays.toString(mDevices) + '}'; } diff --git a/services/core/java/com/android/server/power/stats/IrqDeviceMap.java b/services/core/java/com/android/server/power/stats/wakeups/IrqDeviceMap.java index 091d18e30ccc..8644f720c661 100644 --- a/services/core/java/com/android/server/power/stats/IrqDeviceMap.java +++ b/services/core/java/com/android/server/power/stats/wakeups/IrqDeviceMap.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.power.stats; +package com.android.server.power.stats.wakeups; import android.annotation.XmlRes; import android.content.Context; diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 7718dd88b772..8bbcd2787931 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -97,7 +97,6 @@ import android.window.TransitionInfo; import com.android.internal.app.AssistUtils; import com.android.internal.policy.IKeyguardDismissCallback; -import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import com.android.server.LocalServices; import com.android.server.Watchdog; @@ -1142,18 +1141,11 @@ class ActivityClientController extends IActivityClientController.Stub { // Initiate the transition. final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */, controller, mService.mWindowManager.mSyncEngine); - if (mService.mWindowManager.mSyncEngine.hasActiveSync()) { - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - "Creating Pending Multiwindow Fullscreen Request: %s", transition); - r.mTransitionController.queueCollecting(transition, - () -> { - executeFullscreenRequestTransition(fullscreenRequest, callback, r, - transition, true /* queued */); - }); - } else { - executeFullscreenRequestTransition(fullscreenRequest, callback, r, transition, - false /* queued */); - } + r.mTransitionController.startCollectOrQueue(transition, + (deferred) -> { + executeFullscreenRequestTransition(fullscreenRequest, callback, r, + transition, deferred); + }); } private void executeFullscreenRequestTransition(int fullscreenRequest, IRemoteCallback callback, diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0b98495c8e99..f5cb613601fe 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -306,7 +306,6 @@ import android.os.Bundle; import android.os.Debug; import android.os.IBinder; import android.os.IRemoteCallback; -import android.os.LocaleList; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteCallbackList; @@ -2865,11 +2864,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } - if (animate && mTransitionController.inCollectingTransition(startingWindow) - && startingWindow.cancelAndRedraw()) { + if (animate && mTransitionController.inCollectingTransition(startingWindow)) { // Defer remove starting window after transition start. - // If splash screen window was in collecting, the client side is unable to draw because - // of Session#cancelDraw, which will blocking the remove animation. + // The surface of app window could really show after the transition finish. startingWindow.mSyncTransaction.addTransactionCommittedListener(Runnable::run, () -> { synchronized (mAtmService.mGlobalLock) { surface.remove(true); @@ -4147,7 +4144,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ void handleAppDied() { final boolean remove; - if ((mRelaunchReason == RELAUNCH_REASON_WINDOWING_MODE_RESIZE + if (Process.isSdkSandboxUid(getUid())) { + // Sandbox activities are created for SDKs run in the sandbox process, when the sandbox + // process dies, the SDKs are unloaded and can not handle the activity, so sandbox + // activity records should be removed. + remove = true; + } else if ((mRelaunchReason == RELAUNCH_REASON_WINDOWING_MODE_RESIZE || mRelaunchReason == RELAUNCH_REASON_FREE_RESIZE) && launchCount < 3 && !finishing) { // If the process crashed during a resize, always try to relaunch it, unless it has @@ -9806,7 +9808,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mTransitionController.isShellTransitionsEnabled()) { final Transition transition = new Transition(TRANSIT_RELAUNCH, 0 /* flags */, mTransitionController, mWmService.mSyncEngine); - final Runnable executeRestart = () -> { + mTransitionController.startCollectOrQueue(transition, (deferred) -> { if (mState != RESTARTING_PROCESS || !attachedToProcess()) { transition.abort(); return; @@ -9818,13 +9820,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTransitionController.requestStartTransition(transition, task, null /* remoteTransition */, null /* displayChange */); scheduleStopForRestartProcess(); - }; - if (mWmService.mSyncEngine.hasActiveSync()) { - mTransitionController.queueCollecting(transition, executeRestart); - } else { - mTransitionController.moveToCollecting(transition); - executeRestart.run(); - } + }); } else { startFreezingScreen(); scheduleStopForRestartProcess(); @@ -10597,17 +10593,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } - LocaleList locale; final ActivityTaskManagerInternal.PackageConfig appConfig = mAtmService.mPackageConfigPersister.findPackageConfiguration( task.realActivity.getPackageName(), mUserId); - // if there is no app locale for the package, clear the target activity's locale. - if (appConfig == null || appConfig.mLocales == null || appConfig.mLocales.isEmpty()) { - locale = LocaleList.getEmptyLocaleList(); - } else { - locale = appConfig.mLocales; + // If package lookup yields locales, set the target activity's locales to match, + // otherwise leave target activity as-is. + if (appConfig != null && appConfig.mLocales != null && !appConfig.mLocales.isEmpty()) { + resolvedConfig.setLocales(appConfig.mLocales); } - resolvedConfig.setLocales(locale); } /** diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index e780716dd06c..064af0f3165e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -250,7 +250,6 @@ import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.TransferPipe; import com.android.internal.policy.AttributeCache; import com.android.internal.policy.KeyguardDismissCallback; -import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastPrintWriter; @@ -2873,28 +2872,19 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */, getTransitionController(), mWindowManager.mSyncEngine); - if (mWindowManager.mSyncEngine.hasActiveSync()) { - getTransitionController().queueCollecting(transition, - () -> { - if (!task.getWindowConfiguration().canResizeTask()) { - Slog.w(TAG, "resizeTask not allowed on task=" + task); - transition.abort(); - return; - } - getTransitionController().requestStartTransition(transition, task, - null /* remoteTransition */, null /* displayChange */); - getTransitionController().collect(task); - task.resize(bounds, resizeMode, preserveWindow); - transition.setReady(task, true); - }); - } else { - getTransitionController().moveToCollecting(transition); - getTransitionController().requestStartTransition(transition, task, - null /* remoteTransition */, null /* displayChange */); - getTransitionController().collect(task); - task.resize(bounds, resizeMode, preserveWindow); - transition.setReady(task, true); - } + getTransitionController().startCollectOrQueue(transition, + (deferred) -> { + if (deferred && !task.getWindowConfiguration().canResizeTask()) { + Slog.w(TAG, "resizeTask not allowed on task=" + task); + transition.abort(); + return; + } + getTransitionController().requestStartTransition(transition, task, + null /* remoteTransition */, null /* displayChange */); + getTransitionController().collect(task); + task.resize(bounds, resizeMode, preserveWindow); + transition.setReady(task, true); + }); } } finally { Binder.restoreCallingIdentity(ident); @@ -3625,30 +3615,25 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mActivityClientController.dismissKeyguard(r.token, new KeyguardDismissCallback() { @Override public void onDismissSucceeded() { - if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) { - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - "Creating Pending Pip-Enter: %s", transition); - getTransitionController().queueCollecting(transition, enterPipRunnable); - } else { - // Move to collecting immediately to "claim" the sync-engine for this - // transition. - if (transition != null) { - getTransitionController().moveToCollecting(transition); - } + if (transition == null) { mH.post(enterPipRunnable); + return; } + getTransitionController().startCollectOrQueue(transition, (deferred) -> { + if (deferred) { + enterPipRunnable.run(); + } else { + mH.post(enterPipRunnable); + } + }); } }, null /* message */); } else { // Enter picture in picture immediately otherwise - if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) { - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - "Creating Pending Pip-Enter: %s", transition); - getTransitionController().queueCollecting(transition, enterPipRunnable); + if (transition != null) { + getTransitionController().startCollectOrQueue(transition, + (deferred) -> enterPipRunnable.run()); } else { - if (transition != null) { - getTransitionController().moveToCollecting(transition); - } enterPipRunnable.run(); } } diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 745374301263..11d84ffbd064 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -532,14 +532,7 @@ class BackNavigationController { if (newFocus != null && newFocus != mNavigatingWindow && (newFocus.mActivityRecord == null || (newFocus.mActivityRecord == mNavigatingWindow.mActivityRecord))) { - EventLogTags.writeWmBackNaviCanceled("focusWindowChanged"); - if (isMonitorForRemote()) { - mObserver.sendResult(null /* result */); - } - if (isMonitorAnimationOrTransition()) { - // transition won't happen, cancel internal status - clearBackAnimations(); - } + cancelBackNavigating("focusWindowChanged"); } } @@ -553,19 +546,12 @@ class BackNavigationController { } final ArrayList<WindowContainer> all = new ArrayList<>(opening); all.addAll(closing); - for (WindowContainer app : all) { - if (app.hasChild(mNavigatingWindow)) { - EventLogTags.writeWmBackNaviCanceled("transitionHappens"); - if (isMonitorForRemote()) { - mObserver.sendResult(null /* result */); - } - if (isMonitorAnimationOrTransition()) { - clearBackAnimations(); - } + for (int i = all.size() - 1; i >= 0; --i) { + if (all.get(i).hasChild(mNavigatingWindow)) { + cancelBackNavigating("transitionHappens"); break; } } - } private boolean atSameDisplay(WindowState newFocus) { @@ -575,6 +561,17 @@ class BackNavigationController { final int navigatingDisplayId = mNavigatingWindow.getDisplayId(); return newFocus == null || newFocus.getDisplayId() == navigatingDisplayId; } + + private void cancelBackNavigating(String reason) { + EventLogTags.writeWmBackNaviCanceled(reason); + if (isMonitorForRemote()) { + mObserver.sendResult(null /* result */); + } + if (isMonitorAnimationOrTransition()) { + clearBackAnimations(); + } + cancelPendingAnimation(); + } } // For shell transition @@ -677,12 +674,7 @@ class BackNavigationController { Slog.w(TAG, "Finished transition didn't include the targets" + " open: " + mPendingAnimationBuilder.mOpenTarget + " close: " + mPendingAnimationBuilder.mCloseTarget); - try { - mPendingAnimationBuilder.mBackAnimationAdapter.getRunner().onAnimationCancelled(); - } catch (RemoteException e) { - throw new RuntimeException(e); - } - mPendingAnimationBuilder = null; + cancelPendingAnimation(); return false; } @@ -697,6 +689,18 @@ class BackNavigationController { return true; } + private void cancelPendingAnimation() { + if (mPendingAnimationBuilder == null) { + return; + } + try { + mPendingAnimationBuilder.mBackAnimationAdapter.getRunner().onAnimationCancelled(); + } catch (RemoteException e) { + Slog.e(TAG, "Remote animation gone", e); + } + mPendingAnimationBuilder = null; + } + /** * Create and handling animations status for an open/close animation targets. */ diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java index c6db8a7acb6f..8660becf56a9 100644 --- a/services/core/java/com/android/server/wm/Dimmer.java +++ b/services/core/java/com/android/server/wm/Dimmer.java @@ -263,16 +263,19 @@ class Dimmer { * {@link WindowContainer#prepareSurfaces}. After calling this, the container should * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them * a chance to request dims to continue. - * @return Non-null dim bounds if the dimmer is showing. */ - Rect resetDimStates() { + void resetDimStates() { if (mDimState == null) { - return null; + return; } if (!mDimState.mDontReset) { mDimState.mDimming = false; } - return mDimState.mDimBounds; + } + + /** Returns non-null bounds if the dimmer is showing. */ + Rect getDimBounds() { + return mDimState != null ? mDimState.mDimBounds : null; } void dontAnimateExit() { diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index 26f56a2e5c0b..9f59f5a30caf 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -779,8 +779,9 @@ public class DisplayArea<T extends WindowContainer> extends WindowContainer<T> { @Override void prepareSurfaces() { - final Rect dimBounds = mDimmer.resetDimStates(); + mDimmer.resetDimStates(); super.prepareSurfaces(); + final Rect dimBounds = mDimmer.getDimBounds(); if (dimBounds != null) { // Bounds need to be relative, as the dim layer is a child. getBounds(dimBounds); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index ad934541267e..cd4b3c565a41 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2328,17 +2328,23 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // this as a signal to the transition-player. final Transition transition = new Transition(TRANSIT_SLEEP, 0 /* flags */, display.mTransitionController, mWmService.mSyncEngine); - final Runnable sendSleepTransition = () -> { + final TransitionController.OnStartCollect sendSleepTransition = (deferred) -> { display.mTransitionController.requestStartTransition(transition, null /* trigger */, null /* remote */, null /* display */); // Force playing immediately so that unrelated ops can't be collected. transition.playNow(); }; - if (display.mTransitionController.isCollecting()) { - display.mTransitionController.queueCollecting(transition, sendSleepTransition); - } else { + if (!display.mTransitionController.isCollecting()) { + // Since this bypasses sync, submit directly ignoring whether sync-engine + // is active. + if (mWindowManager.mSyncEngine.hasActiveSync()) { + Slog.w(TAG, "Ongoing sync outside of a transition."); + } display.mTransitionController.moveToCollecting(transition); - sendSleepTransition.run(); + sendSleepTransition.onCollectStarted(false /* deferred */); + } else { + display.mTransitionController.startCollectOrQueue(transition, + sendSleepTransition); } } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 89f975387667..9363eb5cefc6 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -188,7 +188,6 @@ import android.window.WindowContainerToken; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceInteractor; -import com.android.internal.protolog.ProtoLogGroup; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.XmlUtils; import com.android.internal.util.function.pooled.PooledLambda; @@ -3253,9 +3252,10 @@ class Task extends TaskFragment { @Override void prepareSurfaces() { - final Rect dimBounds = mDimmer.resetDimStates(); + mDimmer.resetDimStates(); super.prepareSurfaces(); + final Rect dimBounds = mDimmer.getDimBounds(); if (dimBounds != null) { getDimBounds(dimBounds); @@ -5632,29 +5632,20 @@ class Task extends TaskFragment { final Transition transition = new Transition(TRANSIT_TO_BACK, 0 /* flags */, mTransitionController, mWmService.mSyncEngine); // Guarantee that this gets its own transition by queueing on SyncEngine - if (mWmService.mSyncEngine.hasActiveSync()) { - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - "Creating Pending Move-to-back: %s", transition); - mTransitionController.queueCollecting(transition, - () -> { - // Need to check again since this happens later and the system might - // be in a different state. - if (!canMoveTaskToBack(tr)) { - Slog.e(TAG, "Failed to move task to back after saying we could: " - + tr.mTaskId); - transition.abort(); - return; - } - mTransitionController.requestStartTransition(transition, tr, - null /* remoteTransition */, null /* displayChange */); - moveTaskToBackInner(tr); - }); - } else { - mTransitionController.moveToCollecting(transition); - mTransitionController.requestStartTransition(transition, tr, - null /* remoteTransition */, null /* displayChange */); - moveTaskToBackInner(tr); - } + mTransitionController.startCollectOrQueue(transition, + (deferred) -> { + // Need to check again if deferred since the system might + // be in a different state. + if (deferred && !canMoveTaskToBack(tr)) { + Slog.e(TAG, "Failed to move task to back after saying we could: " + + tr.mTaskId); + transition.abort(); + return; + } + mTransitionController.requestStartTransition(transition, tr, + null /* remoteTransition */, null /* displayChange */); + moveTaskToBackInner(tr); + }); } else { // Skip the transition for pinned task. if (!inPinnedWindowingMode()) { diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index 1d232fe99e3c..311b9a6d2876 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2923,9 +2923,10 @@ class TaskFragment extends WindowContainer<WindowContainer> { return; } - final Rect dimBounds = mDimmer.resetDimStates(); + mDimmer.resetDimStates(); super.prepareSurfaces(); + final Rect dimBounds = mDimmer.getDimBounds(); if (dimBounds != null) { // Bounds need to be relative, as the dim layer is a child. dimBounds.offsetTo(0 /* newLeft */, 0 /* newTop */); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 7ee7025e0580..76b0e7b82ba6 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -287,7 +287,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (restoreBelow != null) { final Task transientRootTask = activity.getRootTask(); - // Collect all visible activities which can be occluded by the transient activity to + // Collect all visible tasks which can be occluded by the transient activity to // make sure they are in the participants so their visibilities can be updated when // finishing transition. ((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> { @@ -297,11 +297,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mTransientHideTasks.add(t); } if (t.isLeafTask()) { - t.forAllActivities(r -> { - if (r.isVisibleRequested()) { - collect(r); - } - }); + collect(t); } } return t == restoreBelow; @@ -904,6 +900,18 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mController.mFinishingTransition = this; if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) { + // Record all the now-hiding activities so that they are committed after + // recalculating visibilities. We just use mParticipants because we can and it will + // ensure proper reporting of `isInFinishTransition`. + for (int i = 0; i < mTransientHideTasks.size(); ++i) { + mTransientHideTasks.get(i).forAllActivities(r -> { + // Only check leaf-tasks that were collected + if (!mParticipants.contains(r.getTask())) return; + // Only concern ourselves with anything that can become invisible + if (!r.isVisible()) return; + mParticipants.add(r); + }); + } // The transient hide tasks could be occluded now, e.g. returning to home. So trigger // the update to make the activities in the tasks invisible-requested, then the next // step can continue to commit the visibility. @@ -953,7 +961,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { enterAutoPip = true; } } - if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) { + final ChangeInfo changeInfo = mChanges.get(ar); + // Due to transient-hide, there may be some activities here which weren't in the + // transition. + if (changeInfo != null && changeInfo.mVisible != visibleAtTransitionEnd) { // Legacy dispatch relies on this (for now). ar.mEnteringAnimation = visibleAtTransitionEnd; } else if (mTransientLaunches != null && mTransientLaunches.containsKey(ar) @@ -2824,7 +2835,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final String name = isDisplayRotation ? "RotationLayer" : "transition snapshot: " + wc; SurfaceControl snapshotSurface = wc.makeAnimationLeash() .setName(name) - .setOpaque(true) + .setOpaque(wc.fillsParent()) .setParent(wc.getSurfaceControl()) .setSecure(screenshotBuffer.containsSecureLayers()) .setCallsite("Transition.ScreenshotSync") diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 4c1c2ff1ac10..cbb4fe2eaa21 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -884,13 +884,30 @@ class TransitionController { proto.end(token); } - void queueCollecting(Transition transit, Runnable onCollectStart) { - mAtm.mWindowManager.mSyncEngine.queueSyncSet( - // Make sure to collect immediately to prevent another transition - // from sneaking in before it. Note: moveToCollecting internally - // calls startSyncSet. - () -> moveToCollecting(transit), - onCollectStart); + /** Returns {@code true} if it started collecting, {@code false} if it was queued. */ + boolean startCollectOrQueue(Transition transit, OnStartCollect onStartCollect) { + if (mAtm.mWindowManager.mSyncEngine.hasActiveSync()) { + if (!isCollecting()) { + Slog.w(TAG, "Ongoing Sync outside of transition."); + } + ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, + "Queueing transition: %s", transit); + mAtm.mWindowManager.mSyncEngine.queueSyncSet( + // Make sure to collect immediately to prevent another transition + // from sneaking in before it. Note: moveToCollecting internally + // calls startSyncSet. + () -> moveToCollecting(transit), + () -> onStartCollect.onCollectStarted(true /* deferred */)); + return false; + } else { + moveToCollecting(transit); + onStartCollect.onCollectStarted(false /* deferred */); + return true; + } + } + + interface OnStartCollect { + void onCollectStarted(boolean deferred); } /** diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index f63470f2bea4..ee86b97e9404 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -280,41 +280,31 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub applyTransaction(t, -1 /* syncId */, null, caller); return null; } - // In cases where transition is already provided, the "readiness lifecycle" of the - // transition is determined outside of this transaction. However, if this is a - // direct call from shell, the entire transition lifecycle is contained in the - // provided transaction and thus we can setReady immediately after apply. - final boolean needsSetReady = transition == null && t != null; final WindowContainerTransaction wct = t != null ? t : new WindowContainerTransaction(); if (transition == null) { if (type < 0) { throw new IllegalArgumentException("Can't create transition with no type"); } - transition = new Transition(type, 0 /* flags */, mTransitionController, - mService.mWindowManager.mSyncEngine); - // If there is already a collecting transition, queue up a new transition and - // return that. The actual start and apply will then be deferred until that - // transition starts collecting. This should almost never happen except during - // tests. - if (mService.mWindowManager.mSyncEngine.hasActiveSync()) { - Slog.w(TAG, "startTransition() while one is already collecting."); - final Transition nextTransition = transition; - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - "Creating Pending Transition: %s", nextTransition); - mTransitionController.queueCollecting(nextTransition, - () -> { - nextTransition.start(); - nextTransition.mLogger.mStartWCT = wct; - applyTransaction(wct, -1 /*syncId*/, nextTransition, caller); - if (needsSetReady) { - nextTransition.setAllReady(); - } - }); - return nextTransition.getToken(); - } - mTransitionController.moveToCollecting(transition); + // This is a direct call from shell, so the entire transition lifecycle is + // contained in the provided transaction if provided. Thus, we can setReady + // immediately after apply. + final boolean needsSetReady = t != null; + final Transition nextTransition = new Transition(type, 0 /* flags */, + mTransitionController, mService.mWindowManager.mSyncEngine); + mTransitionController.startCollectOrQueue(nextTransition, + (deferred) -> { + nextTransition.start(); + nextTransition.mLogger.mStartWCT = wct; + applyTransaction(wct, -1 /*syncId*/, nextTransition, caller); + if (needsSetReady) { + nextTransition.setAllReady(); + } + }); + return nextTransition.getToken(); } + // The transition already started collecting before sending a request to shell, + // so just start here. if (!transition.isCollecting() && !transition.isForcePlaying()) { Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably" + " means Shell took too long to respond to a request. WM State may be" @@ -325,9 +315,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub transition.start(); transition.mLogger.mStartWCT = wct; applyTransaction(wct, -1 /*syncId*/, transition, caller); - if (needsSetReady) { - transition.setAllReady(); - } + // Since the transition is already provided, it means WMCore is determining the + // "readiness lifecycle" outside the provided transaction, so don't set ready here. return transition.getToken(); } } finally { @@ -432,23 +421,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return; } - if (!mService.mWindowManager.mSyncEngine.hasActiveSync()) { - // Sync is for either transition or applySyncTransaction(). We don't support - // multiple sync at the same time because it may cause conflict. - // Create a new transition when there is no active sync to collect the changes. - final Transition transition = mTransitionController.createTransition(type); - if (applyTransaction(wct, -1 /* syncId */, transition, caller) - == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) { - transition.abort(); - return; - } - mTransitionController.requestStartTransition(transition, null /* startTask */, - null /* remoteTransition */, null /* displayChange */); - transition.setAllReady(); - return; - } - - if (!shouldApplyIndependently) { + if (mService.mWindowManager.mSyncEngine.hasActiveSync() + && !shouldApplyIndependently) { // Although there is an active sync, we want to apply the transaction now. // TODO(b/232042367) Redesign the organizer update on activity callback so that we // we will know about the transition explicitly. @@ -467,25 +441,23 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub return; } - // It is ok to queue the WCT until the sync engine is free. - final Transition nextTransition = new Transition(type, 0 /* flags */, + final Transition transition = new Transition(type, 0 /* flags */, mTransitionController, mService.mWindowManager.mSyncEngine); - ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, - "Creating Pending Transition for TaskFragment: %s", nextTransition); - mTransitionController.queueCollecting(nextTransition, - () -> { - if (mTaskFragmentOrganizerController.isValidTransaction(wct) - && (applyTransaction(wct, -1 /* syncId */, nextTransition, caller) - != TRANSACT_EFFECTS_NONE - || !nextTransition.mParticipants.isEmpty())) { - mTransitionController.requestStartTransition(nextTransition, - null /* startTask */, null /* remoteTransition */, - null /* displayChange */); - nextTransition.setAllReady(); - return; - } - nextTransition.abort(); - }); + TransitionController.OnStartCollect doApply = (deferred) -> { + if (deferred && !mTaskFragmentOrganizerController.isValidTransaction(wct)) { + transition.abort(); + return; + } + if (applyTransaction(wct, -1 /* syncId */, transition, caller) + == TRANSACT_EFFECTS_NONE && transition.mParticipants.isEmpty()) { + transition.abort(); + return; + } + mTransitionController.requestStartTransition(transition, null /* startTask */, + null /* remoteTransition */, null /* displayChange */); + transition.setAllReady(); + }; + mTransitionController.startCollectOrQueue(transition, doApply); } finally { Binder.restoreCallingIdentity(ident); } diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index 327483ebbef7..da54b15fd0a4 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -119,24 +119,20 @@ class WindowToken extends WindowContainer<WindowState> { final DisplayInfo mDisplayInfo; final DisplayFrames mDisplayFrames; final Configuration mRotatedOverrideConfiguration; - final SeamlessRotator mRotator; + /** * The tokens that share the same transform. Their end time of transform are the same. The * list should at least contain the token who creates this state. */ final ArrayList<WindowToken> mAssociatedTokens = new ArrayList<>(3); - final ArrayList<WindowContainer<?>> mRotatedContainers = new ArrayList<>(3); + boolean mIsTransforming = true; FixedRotationTransformState(DisplayInfo rotatedDisplayInfo, - DisplayFrames rotatedDisplayFrames, Configuration rotatedConfig, - int currentRotation) { + DisplayFrames rotatedDisplayFrames, Configuration rotatedConfig) { mDisplayInfo = rotatedDisplayInfo; mDisplayFrames = rotatedDisplayFrames; mRotatedOverrideConfiguration = rotatedConfig; - // This will use unrotate as rotate, so the new and old rotation are inverted. - mRotator = new SeamlessRotator(rotatedDisplayInfo.rotation, currentRotation, - rotatedDisplayInfo, true /* applyFixedTransformationHint */); } /** @@ -144,10 +140,8 @@ class WindowToken extends WindowContainer<WindowState> { * showing the window in a display with different rotation. */ void transform(WindowContainer<?> container) { - mRotator.unrotate(container.getPendingTransaction(), container); - if (!mRotatedContainers.contains(container)) { - mRotatedContainers.add(container); - } + // The default implementation assumes shell transition is enabled, so the transform + // is done by getOrCreateFixedRotationLeash(). } /** @@ -155,6 +149,40 @@ class WindowToken extends WindowContainer<WindowState> { * be called when the window has the same rotation as display. */ void resetTransform() { + for (int i = mAssociatedTokens.size() - 1; i >= 0; --i) { + mAssociatedTokens.get(i).removeFixedRotationLeash(); + } + } + + /** The state may not only be used by self. Make sure to leave the influence by others. */ + void disassociate(WindowToken token) { + mAssociatedTokens.remove(token); + } + } + + private static class FixedRotationTransformStateLegacy extends FixedRotationTransformState { + final SeamlessRotator mRotator; + final ArrayList<WindowContainer<?>> mRotatedContainers = new ArrayList<>(3); + + FixedRotationTransformStateLegacy(DisplayInfo rotatedDisplayInfo, + DisplayFrames rotatedDisplayFrames, Configuration rotatedConfig, + int currentRotation) { + super(rotatedDisplayInfo, rotatedDisplayFrames, rotatedConfig); + // This will use unrotate as rotate, so the new and old rotation are inverted. + mRotator = new SeamlessRotator(rotatedDisplayInfo.rotation, currentRotation, + rotatedDisplayInfo, true /* applyFixedTransformationHint */); + } + + @Override + void transform(WindowContainer<?> container) { + mRotator.unrotate(container.getPendingTransaction(), container); + if (!mRotatedContainers.contains(container)) { + mRotatedContainers.add(container); + } + } + + @Override + void resetTransform() { for (int i = mRotatedContainers.size() - 1; i >= 0; i--) { final WindowContainer<?> c = mRotatedContainers.get(i); // If the window is detached (no parent), its surface may have been released. @@ -164,9 +192,9 @@ class WindowToken extends WindowContainer<WindowState> { } } - /** The state may not only be used by self. Make sure to leave the influence by others. */ + @Override void disassociate(WindowToken token) { - mAssociatedTokens.remove(token); + super.disassociate(token); mRotatedContainers.remove(token); } } @@ -437,8 +465,11 @@ class WindowToken extends WindowContainer<WindowState> { if (mFixedRotationTransformState != null) { mFixedRotationTransformState.disassociate(this); } - mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames, - new Configuration(config), mDisplayContent.getRotation()); + config = new Configuration(config); + mFixedRotationTransformState = mTransitionController.isShellTransitionsEnabled() + ? new FixedRotationTransformState(info, displayFrames, config) + : new FixedRotationTransformStateLegacy(info, displayFrames, config, + mDisplayContent.getRotation()); mFixedRotationTransformState.mAssociatedTokens.add(this); mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames); onFixedRotationStatePrepared(); @@ -508,14 +539,7 @@ class WindowToken extends WindowContainer<WindowState> { if (state == null) { return; } - if (!mTransitionController.isShellTransitionsEnabled()) { - state.resetTransform(); - } else { - // Remove all the leashes - for (int i = state.mAssociatedTokens.size() - 1; i >= 0; --i) { - state.mAssociatedTokens.get(i).removeFixedRotationLeash(); - } - } + state.resetTransform(); // Clear the flag so if the display will be updated to the same orientation, the transform // won't take effect. state.mIsTransforming = false; @@ -589,7 +613,9 @@ class WindowToken extends WindowContainer<WindowState> { void removeFixedRotationLeash() { if (mFixedRotationTransformLeash == null) return; final SurfaceControl.Transaction t = getSyncTransaction(); - t.reparent(getSurfaceControl(), getParentSurfaceControl()); + if (mSurfaceControl != null) { + t.reparent(mSurfaceControl, getParentSurfaceControl()); + } t.remove(mFixedRotationTransformLeash); mFixedRotationTransformLeash = null; } diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index 4cf14c7f111d..fc7fd1afe58f 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -91,14 +91,16 @@ public final class CreateRequestSession extends RequestSession<CreateCredentialR protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) { mRequestSessionMetric.collectUiCallStartTime(System.nanoTime()); mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION); + cancelExistingPendingIntent(); try { - mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent( + mPendingIntent = mCredentialManagerUi.createPendingIntent( RequestInfo.newCreateRequestInfo( mRequestId, mClientRequest, mClientAppInfo.getPackageName(), PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(), Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)), - providerDataList)); + providerDataList); + mClientCallback.onPendingIntent(mPendingIntent); } catch (RemoteException e) { mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false); mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED); diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java index 87509067f993..0dee7a44375d 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java @@ -33,7 +33,6 @@ import android.os.IBinder; import android.os.Looper; import android.os.ResultReceiver; import android.service.credentials.CredentialProviderInfoFactory; -import android.util.Log; import android.util.Slog; import java.util.ArrayList; @@ -124,7 +123,6 @@ public class CredentialManagerUi { public CredentialManagerUi(Context context, int userId, CredentialManagerUiCallback callbacks, Set<ComponentName> enabledProviders) { - Log.i(TAG, "In CredentialManagerUi constructor"); mContext = context; mUserId = userId; mCallbacks = callbacks; @@ -151,8 +149,6 @@ public class CredentialManagerUi { */ public PendingIntent createPendingIntent( RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) { - Log.i(TAG, "In createPendingIntent"); - List<CredentialProviderInfo> allProviders = CredentialProviderInfoFactory.getCredentialProviderServices( mContext, diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index e329cec95049..f39de43b5076 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -90,11 +90,13 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) { mRequestSessionMetric.collectUiCallStartTime(System.nanoTime()); mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION); + cancelExistingPendingIntent(); try { - mClientCallback.onPendingIntent(mCredentialManagerUi.createPendingIntent( + mPendingIntent = mCredentialManagerUi.createPendingIntent( RequestInfo.newGetRequestInfo( mRequestId, mClientRequest, mClientAppInfo.getPackageName()), - providerDataList)); + providerDataList); + mClientCallback.onPendingIntent(mPendingIntent); } catch (RemoteException e) { mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false); mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED); diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java index c1e9bc6a5c3c..0ad73c945284 100644 --- a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java +++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java @@ -40,7 +40,6 @@ import android.service.credentials.IBeginGetCredentialCallback; import android.service.credentials.IClearCredentialStateCallback; import android.service.credentials.ICredentialProviderService; import android.text.format.DateUtils; -import android.util.Log; import android.util.Slog; import com.android.internal.infra.ServiceConnector; @@ -122,7 +121,6 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr */ public void onBeginGetCredential(@NonNull BeginGetCredentialRequest request, ProviderCallbacks<BeginGetCredentialResponse> callback) { - Log.i(TAG, "In onGetCredentials in RemoteCredentialService"); AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); AtomicReference<CompletableFuture<BeginGetCredentialResponse>> futureRef = new AtomicReference<>(); @@ -142,7 +140,6 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr @Override public void onFailure(String errorType, CharSequence message) { - Log.i(TAG, "In onFailure in RemoteCredentialService"); String errorMsg = message == null ? "" : String.valueOf( message); getCredentials.completeExceptionally( @@ -182,7 +179,6 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr */ public void onBeginCreateCredential(@NonNull BeginCreateCredentialRequest request, ProviderCallbacks<BeginCreateCredentialResponse> callback) { - Log.i(TAG, "In onCreateCredential in RemoteCredentialService"); AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); AtomicReference<CompletableFuture<BeginCreateCredentialResponse>> futureRef = new AtomicReference<>(); @@ -197,14 +193,11 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr request, new IBeginCreateCredentialCallback.Stub() { @Override public void onSuccess(BeginCreateCredentialResponse response) { - Log.i(TAG, "In onSuccess onBeginCreateCredential " - + "in RemoteCredentialService"); createCredentialFuture.complete(response); } @Override public void onFailure(String errorType, CharSequence message) { - Log.i(TAG, "In onFailure in RemoteCredentialService"); String errorMsg = message == null ? "" : String.valueOf( message); createCredentialFuture.completeExceptionally( @@ -244,7 +237,6 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr */ public void onClearCredentialState(@NonNull ClearCredentialStateRequest request, ProviderCallbacks<Void> callback) { - Log.i(TAG, "In onClearCredentialState in RemoteCredentialService"); AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>(); AtomicReference<CompletableFuture<Void>> futureRef = new AtomicReference<>(); @@ -258,14 +250,11 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr request, new IClearCredentialStateCallback.Stub() { @Override public void onSuccess() { - Log.i(TAG, "In onSuccess onClearCredentialState " - + "in RemoteCredentialService"); clearCredentialFuture.complete(null); } @Override public void onFailure(String errorType, CharSequence message) { - Log.i(TAG, "In onFailure in RemoteCredentialService"); String errorMsg = message == null ? "" : String.valueOf(message); clearCredentialFuture.completeExceptionally( @@ -300,35 +289,29 @@ public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialPr AtomicReference<ICancellationSignal> cancellationSink, ProviderCallbacks<T> callback) { if (error == null) { - Log.i(TAG, "In RemoteCredentialService execute error is null"); callback.onProviderResponseSuccess(result); } else { if (error instanceof TimeoutException) { - Log.i(TAG, "In RemoteCredentialService execute error is timeout"); + Slog.d(TAG, "Remote provider response timed tuo for: " + mComponentName); dispatchCancellationSignal(cancellationSink.get()); callback.onProviderResponseFailure( CredentialProviderErrors.ERROR_TIMEOUT, null); } else if (error instanceof CancellationException) { - Log.i(TAG, "In RemoteCredentialService execute error is cancellation"); + Slog.d(TAG, "Cancellation exception for remote provider: " + mComponentName); dispatchCancellationSignal(cancellationSink.get()); callback.onProviderResponseFailure( CredentialProviderErrors.ERROR_TASK_CANCELED, null); } else if (error instanceof GetCredentialException) { - Log.i(TAG, "In RemoteCredentialService execute error is provider get" - + "error"); callback.onProviderResponseFailure( CredentialProviderErrors.ERROR_PROVIDER_FAILURE, (GetCredentialException) error); } else if (error instanceof CreateCredentialException) { - Log.i(TAG, "In RemoteCredentialService execute error is provider create " - + "error"); callback.onProviderResponseFailure( CredentialProviderErrors.ERROR_PROVIDER_FAILURE, (CreateCredentialException) error); } else { - Log.i(TAG, "In RemoteCredentialService execute error is unknown"); callback.onProviderResponseFailure( CredentialProviderErrors.ERROR_UNKNOWN, (Exception) error); diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java index 8fd02691e190..15a30e427688 100644 --- a/services/credentials/java/com/android/server/credentials/RequestSession.java +++ b/services/credentials/java/com/android/server/credentials/RequestSession.java @@ -18,6 +18,7 @@ package com.android.server.credentials; import android.annotation.NonNull; import android.annotation.UserIdInt; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -90,6 +91,8 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential private final Set<ComponentName> mEnabledProviders; + protected PendingIntent mPendingIntent; + @NonNull protected RequestSessionStatus mRequestSessionStatus = RequestSessionStatus.IN_PROGRESS; @@ -202,11 +205,23 @@ abstract class RequestSession<T, U, V> implements CredentialManagerUi.Credential if (propagateCancellation) { mProviders.values().forEach(ProviderSession::cancelProviderRemoteSession); } + cancelExistingPendingIntent(); mRequestSessionStatus = RequestSessionStatus.COMPLETE; mProviders.clear(); clearRequestSessionLocked(); } + void cancelExistingPendingIntent() { + if (mPendingIntent != null) { + try { + mPendingIntent.cancel(); + mPendingIntent = null; + } catch (Exception e) { + Slog.e(TAG, "Unable to cancel existing pending intent", e); + } + } + } + private void clearRequestSessionLocked() { synchronized (mLock) { mSessionCallback.onFinishRequestSession(mUserId, mRequestId); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index 926c7e400144..702602adf1b3 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -792,7 +792,7 @@ final class DevicePolicyEngine { admin.getUserId()); if (receivers.isEmpty()) { Log.i(TAG, "Couldn't find any receivers that handle ACTION_DEVICE_POLICY_SET_RESULT" - + "in package " + admin.getPackageName()); + + " in package " + admin.getPackageName()); return; } @@ -845,7 +845,7 @@ final class DevicePolicyEngine { admin.getUserId()); if (receivers.isEmpty()) { Log.i(TAG, "Couldn't find any receivers that handle ACTION_DEVICE_POLICY_CHANGED" - + "in package " + admin.getPackageName()); + + " in package " + admin.getPackageName()); return; } @@ -868,7 +868,7 @@ final class DevicePolicyEngine { for (ResolveInfo resolveInfo : receivers) { if (!Manifest.permission.BIND_DEVICE_ADMIN.equals( resolveInfo.activityInfo.permission)) { - Log.w(TAG, "Receiver " + resolveInfo.activityInfo + " is not protected by" + Log.w(TAG, "Receiver " + resolveInfo.activityInfo + " is not protected by " + "BIND_DEVICE_ADMIN permission!"); continue; } @@ -910,7 +910,7 @@ final class DevicePolicyEngine { mDeviceAdminServiceController.stopServicesForUser( userId, actionForLog); } else { - for (EnforcingAdmin admin : getEnforcingAdminsForUser(userId)) { + for (EnforcingAdmin admin : getEnforcingAdminsOnUser(userId)) { // DPCs are handled separately in DPMS, no need to reestablish the connection here. if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) { continue; @@ -921,6 +921,51 @@ final class DevicePolicyEngine { } } + /** + * Handles internal state related to a user getting started. + */ + void handleStartUser(int userId) { + updateDeviceAdminsServicesForUser( + userId, /* enable= */ true, /* actionForLog= */ "start-user"); + } + + /** + * Handles internal state related to a user getting started. + */ + void handleUnlockUser(int userId) { + updateDeviceAdminsServicesForUser( + userId, /* enable= */ true, /* actionForLog= */ "unlock-user"); + } + + /** + * Handles internal state related to a user getting stopped. + */ + void handleStopUser(int userId) { + updateDeviceAdminsServicesForUser( + userId, /* enable= */ false, /* actionForLog= */ "stop-user"); + } + + /** + * Handles internal state related to packages getting updated. + */ + void handlePackageChanged(@Nullable String updatedPackage, int userId) { + if (updatedPackage == null) { + return; + } + updateDeviceAdminServiceOnPackageChanged(updatedPackage, userId); + } + + /** + * Handles internal state related to a user getting removed. + */ + void handleUserRemoved(int userId) { + removeLocalPoliciesForUser(userId); + removePoliciesForAdminsOnUser(userId); + } + + /** + * Handles internal state related to a user getting created. + */ void handleUserCreated(UserInfo user) { enforcePoliciesOnInheritableProfilesIfApplicable(user); } @@ -963,40 +1008,6 @@ final class DevicePolicyEngine { } /** - * Handles internal state related to a user getting started. - */ - void handleStartUser(int userId) { - updateDeviceAdminsServicesForUser( - userId, /* enable= */ true, /* actionForLog= */ "start-user"); - } - - /** - * Handles internal state related to a user getting started. - */ - void handleUnlockUser(int userId) { - updateDeviceAdminsServicesForUser( - userId, /* enable= */ true, /* actionForLog= */ "unlock-user"); - } - - /** - * Handles internal state related to a user getting stopped. - */ - void handleStopUser(int userId) { - updateDeviceAdminsServicesForUser( - userId, /* enable= */ false, /* actionForLog= */ "stop-user"); - } - - /** - * Handles internal state related to packages getting updated. - */ - void handlePackageChanged(@Nullable String updatedPackage, int userId) { - if (updatedPackage == null) { - return; - } - updateDeviceAdminServiceOnPackageChanged(updatedPackage, userId); - } - - /** * Returns all current enforced policies set on the device, and the individual values set by * each admin. Global policies are returned under {@link UserHandle#ALL}. */ @@ -1024,6 +1035,68 @@ final class DevicePolicyEngine { return new DevicePolicyState(policies); } + + /** + * Removes all local and global policies set by that admin. + */ + void removePoliciesForAdmin(EnforcingAdmin admin) { + Set<PolicyKey> globalPolicies = new HashSet<>(mGlobalPolicies.keySet()); + for (PolicyKey policy : globalPolicies) { + PolicyState<?> policyState = mGlobalPolicies.get(policy); + if (policyState.getPoliciesSetByAdmins().containsKey(admin)) { + removeGlobalPolicy(policyState.getPolicyDefinition(), admin); + } + } + + for (int i = 0; i < mLocalPolicies.size(); i++) { + Set<PolicyKey> localPolicies = new HashSet<>( + mLocalPolicies.get(mLocalPolicies.keyAt(i)).keySet()); + for (PolicyKey policy : localPolicies) { + PolicyState<?> policyState = mLocalPolicies.get( + mLocalPolicies.keyAt(i)).get(policy); + if (policyState.getPoliciesSetByAdmins().containsKey(admin)) { + removeLocalPolicy( + policyState.getPolicyDefinition(), admin, mLocalPolicies.keyAt(i)); + } + } + } + } + + /** + * Removes all local policies for the provided {@code userId}. + */ + private void removeLocalPoliciesForUser(int userId) { + if (!mLocalPolicies.contains(userId)) { + // No policies on user + return; + } + + Set<PolicyKey> localPolicies = new HashSet<>(mLocalPolicies.get(userId).keySet()); + for (PolicyKey policy : localPolicies) { + PolicyState<?> policyState = mLocalPolicies.get(userId).get(policy); + Set<EnforcingAdmin> admins = new HashSet<>( + policyState.getPoliciesSetByAdmins().keySet()); + for (EnforcingAdmin admin : admins) { + removeLocalPolicy( + policyState.getPolicyDefinition(), admin, userId); + } + } + + mLocalPolicies.remove(userId); + } + + /** + * Removes all local and global policies for admins installed in the provided + * {@code userId}. + */ + private void removePoliciesForAdminsOnUser(int userId) { + Set<EnforcingAdmin> admins = getEnforcingAdminsOnUser(userId); + + for (EnforcingAdmin admin : admins) { + removePoliciesForAdmin(admin); + } + } + /** * Reestablishes the service that handles * {@link DevicePolicyManager#ACTION_DEVICE_ADMIN_SERVICE} in the enforcing admin if the package @@ -1031,7 +1104,7 @@ final class DevicePolicyEngine { */ private void updateDeviceAdminServiceOnPackageChanged( @NonNull String updatedPackage, int userId) { - for (EnforcingAdmin admin : getEnforcingAdminsForUser(userId)) { + for (EnforcingAdmin admin : getEnforcingAdminsOnUser(userId)) { // DPCs are handled separately in DPMS, no need to reestablish the connection here. if (admin.hasAuthority(EnforcingAdmin.DPC_AUTHORITY)) { continue; @@ -1120,7 +1193,7 @@ final class DevicePolicyEngine { } @NonNull - private Set<EnforcingAdmin> getEnforcingAdminsForUser(int userId) { + private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) { return mEnforcingAdmins.contains(userId) ? mEnforcingAdmins.get(userId) : Collections.emptySet(); } @@ -1159,12 +1232,6 @@ final class DevicePolicyEngine { } } - // TODO: we need to listen for user removal and package removal and update out internal policy - // map and enforcing admins for this is be accurate. - boolean hasActivePolicies() { - return mEnforcingAdmins.size() > 0; - } - private <V> boolean checkFor2gFailure(@NonNull PolicyDefinition<V> policyDefinition, @NonNull EnforcingAdmin enforcingAdmin) { if (!policyDefinition.getPolicyKey().getIdentifier().equals( diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 29f9a306880f..5cad4e208b3f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -856,7 +856,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = "enable_device_policy_engine"; - private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = false; + private static final boolean DEFAULT_ENABLE_DEVICE_POLICY_ENGINE_FOR_FINANCE_FLAG = true; // TODO(b/265683382) remove the flag after rollout. private static final String KEEP_PROFILES_RUNNING_FLAG = "enable_keep_profiles_running"; @@ -1178,6 +1178,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Resume logging if all remaining users are affiliated. maybeResumeDeviceWideLoggingLocked(); } + if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) { + mDevicePolicyEngine.handleUserRemoved(userHandle); + } } } else if (Intent.ACTION_USER_STARTED.equals(action)) { sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_STARTED, userHandle); @@ -3664,6 +3667,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } for (Integer userId : deletedUsers) { removeUserData(userId); + if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) { + mDevicePolicyEngine.handleUserRemoved(userId); + } } } @@ -4153,6 +4159,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mInjector.binderWithCleanCallingIdentity(() -> removeActiveAdminLocked(adminReceiver, userHandle)); + if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) { + mDevicePolicyEngine.removePoliciesForAdmin( + EnforcingAdmin.createEnterpriseEnforcingAdmin( + adminReceiver, userHandle, admin)); + } } } @@ -9992,6 +10003,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { toggleBackupServiceActive(UserHandle.USER_SYSTEM, true); pushUserControlDisabledPackagesLocked(userId); setGlobalSettingDeviceOwnerType(DEVICE_OWNER_TYPE_DEFAULT); + + if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) { + mDevicePolicyEngine.removePoliciesForAdmin( + EnforcingAdmin.createEnterpriseEnforcingAdmin( + admin.info.getComponent(), userId, admin)); + } } private void clearApplicationRestrictions(int userId) { @@ -10139,6 +10156,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { toggleBackupServiceActive(userId, true); applyProfileRestrictionsIfDeviceOwnerLocked(); setNetworkLoggingActiveInternal(false); + + if (isPolicyEngineForFinanceFlagEnabled() || isPermissionCheckFlagEnabled()) { + mDevicePolicyEngine.removePoliciesForAdmin( + EnforcingAdmin.createEnterpriseEnforcingAdmin( + admin.info.getComponent(), userId, admin)); + } } @Override @@ -13264,6 +13287,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ? getProfileParentId(caller.getUserId()) : caller.getUserId(); setLocalUserRestrictionInternal(admin, key, enabled, affectedUserId); } + } else { + throw new IllegalStateException("Non-DO/Non-PO cannot set restriction " + key + + " while targetSdkVersion is less than UPSIDE_DOWN_CAKE"); } } } @@ -13292,14 +13318,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /* who= */ null, key, caller.getPackageName(), - caller.getUserId() + UserHandle.USER_ALL ); setGlobalUserRestrictionInternal(admin, key, /* enabled= */ true); logUserRestrictionCall(key, /* enabled= */ true, /* parent= */ false, caller); } - private void setLocalUserRestrictionInternal( EnforcingAdmin admin, String key, boolean enabled, int userId) { PolicyDefinition<Boolean> policyDefinition = @@ -13317,7 +13342,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { userId); } } - private void setGlobalUserRestrictionInternal( EnforcingAdmin admin, String key, boolean enabled) { PolicyDefinition<Boolean> policyDefinition = @@ -14701,7 +14725,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final int userId = mInjector.userHandleGetCallingUserId(); - if (isPermissionCheckFlagEnabled()) { + if (isPolicyEngineForFinanceFlagEnabled()) { LockTaskPolicy policy = mDevicePolicyEngine.getResolvedPolicy( PolicyDefinition.LOCK_TASK, userId); if (policy == null) { @@ -16088,6 +16112,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else { long ident = mInjector.binderClearCallingIdentity(); try { + // TODO(b/277908283): check in the policy engine instead of calling user manager. List<UserManager.EnforcingUser> sources = mUserManager .getUserRestrictionSources(restriction, UserHandle.of(userId)); if (sources == null) { @@ -16119,27 +16144,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final UserManager.EnforcingUser enforcingUser = sources.get(0); final int sourceType = enforcingUser.getUserRestrictionSource(); - final int enforcingUserId = enforcingUser.getUserHandle().getIdentifier(); - if (sourceType == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER) { - // Restriction was enforced by PO - final ComponentName profileOwner = mOwners.getProfileOwnerComponent( - enforcingUserId); - if (profileOwner != null) { - result = new Bundle(); - result.putInt(Intent.EXTRA_USER_ID, enforcingUserId); - result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN, - profileOwner); - return result; - } - } else if (sourceType == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) { - // Restriction was enforced by DO - final Pair<Integer, ComponentName> deviceOwner = - mOwners.getDeviceOwnerUserIdAndComponent(); - if (deviceOwner != null) { + if (sourceType == UserManager.RESTRICTION_SOURCE_PROFILE_OWNER + || sourceType == UserManager.RESTRICTION_SOURCE_DEVICE_OWNER) { + ActiveAdmin admin = getMostProbableDPCAdminForLocalPolicy(userId); + if (admin != null) { result = new Bundle(); - result.putInt(Intent.EXTRA_USER_ID, deviceOwner.first); + result.putInt(Intent.EXTRA_USER_ID, admin.getUserHandle().getIdentifier()); result.putParcelable(DevicePolicyManager.EXTRA_DEVICE_ADMIN, - deviceOwner.second); + admin.info.getComponent()); return result; } } else if (sourceType == UserManager.RESTRICTION_SOURCE_SYSTEM) { @@ -17353,6 +17365,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { caller.getUserId()); admin = enforcingAdmin.getActiveAdmin(); } else { + Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); } @@ -17384,6 +17397,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { caller.getUserId()); admin = enforcingAdmin.getActiveAdmin(); } else { + Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkCallingUser(isManagedProfile(caller.getUserId())); Preconditions.checkCallAuthorization(isDeviceOwner(caller) || isProfileOwner(caller)); @@ -22549,53 +22563,53 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { ); /** - * All the permisisons granted to a profile owner. + * All the permissions granted to a profile owner. */ private static final List<String> PROFILE_OWNER_PERMISSIONS = List.of( - MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, - MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL, - MANAGE_DEVICE_POLICY_APPS_CONTROL, - MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, - MANAGE_DEVICE_POLICY_AUDIO_OUTPUT, - MANAGE_DEVICE_POLICY_AUTOFILL, - MANAGE_DEVICE_POLICY_CALLS, - MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES, - MANAGE_DEVICE_POLICY_DISPLAY, - MANAGE_DEVICE_POLICY_FACTORY_RESET, - MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES, - MANAGE_DEVICE_POLICY_KEYGUARD, - MANAGE_DEVICE_POLICY_LOCALE, - MANAGE_DEVICE_POLICY_LOCATION, - MANAGE_DEVICE_POLICY_LOCK, - MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, - MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION, - MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, - MANAGE_DEVICE_POLICY_PACKAGE_STATE, - MANAGE_DEVICE_POLICY_PRINTING, - MANAGE_DEVICE_POLICY_PROFILES, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, - MANAGE_DEVICE_POLICY_RESET_PASSWORD, - MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, - MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, - MANAGE_DEVICE_POLICY_SCREEN_CONTENT, - MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, - MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS, - MANAGE_DEVICE_POLICY_TIME, - MANAGE_DEVICE_POLICY_VPN, - MANAGE_DEVICE_POLICY_WIPE_DATA + MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT, + MANAGE_DEVICE_POLICY_ACROSS_USERS_SECURITY_CRITICAL, + MANAGE_DEVICE_POLICY_APPS_CONTROL, + MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, + MANAGE_DEVICE_POLICY_AUDIO_OUTPUT, + MANAGE_DEVICE_POLICY_AUTOFILL, + MANAGE_DEVICE_POLICY_BLUETOOTH, + MANAGE_DEVICE_POLICY_CALLS, + MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES, + MANAGE_DEVICE_POLICY_DISPLAY, + MANAGE_DEVICE_POLICY_FACTORY_RESET, + MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES, + MANAGE_DEVICE_POLICY_KEYGUARD, + MANAGE_DEVICE_POLICY_LOCALE, + MANAGE_DEVICE_POLICY_LOCATION, + MANAGE_DEVICE_POLICY_LOCK, + MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS, + MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION, + MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, + MANAGE_DEVICE_POLICY_PACKAGE_STATE, + MANAGE_DEVICE_POLICY_PRINTING, + MANAGE_DEVICE_POLICY_PROFILES, + MANAGE_DEVICE_POLICY_PROFILE_INTERACTION, + MANAGE_DEVICE_POLICY_RESET_PASSWORD, + MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, + MANAGE_DEVICE_POLICY_SCREEN_CAPTURE, + MANAGE_DEVICE_POLICY_SCREEN_CONTENT, + MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE, + MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS, + MANAGE_DEVICE_POLICY_TIME, + MANAGE_DEVICE_POLICY_VPN, + MANAGE_DEVICE_POLICY_WIPE_DATA ); /** - * All the additional permissions granted to an organisation owned profile owner. - */ + * All the additional permissions granted to an organisation owned profile owner. + */ private static final List<String> ADDITIONAL_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE_PERMISSIONS = - List.of( + List.of( MANAGE_DEVICE_POLICY_ACROSS_USERS, MANAGE_DEVICE_POLICY_AIRPLANE_MODE, MANAGE_DEVICE_POLICY_APPS_CONTROL, - MANAGE_DEVICE_POLICY_BLUETOOTH, MANAGE_DEVICE_POLICY_CAMERA, MANAGE_DEVICE_POLICY_CERTIFICATES, MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, @@ -22616,13 +22630,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_WIFI, SET_TIME, SET_TIME_ZONE - ); + ); private static final List<String> ADDITIONAL_PROFILE_OWNER_ON_USER_0_PERMISSIONS = List.of( MANAGE_DEVICE_POLICY_AIRPLANE_MODE, - MANAGE_DEVICE_POLICY_BLUETOOTH, MANAGE_DEVICE_POLICY_CAMERA, MANAGE_DEVICE_POLICY_DISPLAY, MANAGE_DEVICE_POLICY_FUN, diff --git a/services/tests/PackageManagerServiceTests/TEST_MAPPING b/services/tests/PackageManagerServiceTests/TEST_MAPPING index e98acb2bb6a9..5d96af9df1fb 100644 --- a/services/tests/PackageManagerServiceTests/TEST_MAPPING +++ b/services/tests/PackageManagerServiceTests/TEST_MAPPING @@ -55,23 +55,10 @@ // TODO(b/204133664) "exclude-filter": "com.android.server.pm.test.SdCardEjectionTests" }, - { - // TODO(b/272575212) - "exclude-filter": "com.android.server.pm.test.SettingsTest#testWriteCorruptDataBinaryXml" - }, - { - "exclude-filter": "com.android.server.pm.test.SettingsTest#testWriteCorruptDataTextXml" - }, - { - "exclude-filter": "com.android.server.pm.test.SettingsTest#testWriteCorruptHeaderBinaryXml" - }, - { - "exclude-filter": "com.android.server.pm.test.SettingsTest#testWriteCorruptHeaderTextXml" - }, - { + { // TODO(b/272714903) "exclude-filter": "com.android.server.pm.test.OverlayPathsUninstallSystemUpdatesTest#verify" - } + } ] } ], 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 ec177c9ac33d..3fb7fb4e6aea 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -74,16 +74,17 @@ import android.os.BundleMerger; import android.os.DropBoxManager; import android.os.HandlerThread; import android.os.SystemClock; +import android.os.TestLooperManager; import android.os.UserHandle; import android.provider.Settings; import android.util.IndentingPrintWriter; import android.util.Pair; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.util.FrameworkStatsLog; import com.android.server.ExtendedMockitoRule; -import com.android.server.am.BroadcastQueueTest.SyncBarrier; import org.junit.After; import org.junit.Before; @@ -96,6 +97,7 @@ import java.io.Writer; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; +import java.util.Objects; @SmallTest public final class BroadcastQueueModernImplTest { @@ -111,6 +113,7 @@ public final class BroadcastQueueModernImplTest { @Mock BroadcastProcessQueue mQueue4; HandlerThread mHandlerThread; + TestLooperManager mLooper; BroadcastConstants mConstants; BroadcastQueueModernImpl mImpl; @@ -127,6 +130,10 @@ public final class BroadcastQueueModernImplTest { mHandlerThread = new HandlerThread(getClass().getSimpleName()); mHandlerThread.start(); + // Pause all event processing until a test chooses to resume + mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation() + .acquireLooperManager(mHandlerThread.getLooper())); + mConstants = new BroadcastConstants(Settings.Global.BROADCAST_FG_CONSTANTS); mConstants.DELAY_URGENT_MILLIS = -120_000; mConstants.DELAY_NORMAL_MILLIS = 10_000; @@ -167,6 +174,17 @@ public final class BroadcastQueueModernImplTest { mHandlerThread.quit(); } + /** + * Un-pause our handler to process pending events, wait for our queue to go + * idle, and then re-pause the handler. + */ + private void waitForIdle() throws Exception { + mLooper.release(); + mImpl.waitForIdle(LOG_WRITER_INFO); + mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation() + .acquireLooperManager(mHandlerThread.getLooper())); + } + private static void assertOrphan(BroadcastProcessQueue queue) { assertNull(queue.runnableAtNext); assertNull(queue.runnableAtPrev); @@ -237,9 +255,23 @@ public final class BroadcastQueueModernImplTest { } private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue, + BroadcastRecord record, int recordIndex) { + enqueueOrReplaceBroadcast(queue, record, recordIndex, false, 42_000_000L); + } + + private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue, BroadcastRecord record, int recordIndex, long enqueueTime) { - queue.enqueueOrReplaceBroadcast(record, recordIndex, false); + enqueueOrReplaceBroadcast(queue, record, recordIndex, false, enqueueTime); + } + + private void enqueueOrReplaceBroadcast(BroadcastProcessQueue queue, + BroadcastRecord record, int recordIndex, boolean wouldBeSkipped, long enqueueTime) { + queue.enqueueOrReplaceBroadcast(record, recordIndex, wouldBeSkipped, (r, i) -> { + throw new UnsupportedOperationException(); + }); record.enqueueTime = enqueueTime; + record.enqueueRealTime = enqueueTime; + record.enqueueClockTime = enqueueTime; } @Test @@ -370,7 +402,7 @@ public final class BroadcastQueueModernImplTest { .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE); final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options, List.of(makeMockRegisteredReceiver()), false); - queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false); + enqueueOrReplaceBroadcast(queue, airplaneRecord, 0); queue.setProcessAndUidCached(null, false); final long notCachedRunnableAt = queue.getRunnableAt(); @@ -397,7 +429,7 @@ public final class BroadcastQueueModernImplTest { .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_NONE); final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, options, List.of(makeMockRegisteredReceiver()), false); - queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false); + enqueueOrReplaceBroadcast(queue, airplaneRecord, 0); queue.setProcessAndUidCached(null, false); final long notCachedRunnableAt = queue.getRunnableAt(); @@ -421,12 +453,12 @@ public final class BroadcastQueueModernImplTest { // enqueue a bg-priority broadcast then a fg-priority one final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone); - queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, false); + enqueueOrReplaceBroadcast(queue, timezoneRecord, 0); final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane); - queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false); + enqueueOrReplaceBroadcast(queue, airplaneRecord, 0); // verify that: // (a) the queue is immediately runnable by existence of a fg-priority broadcast @@ -457,7 +489,7 @@ public final class BroadcastQueueModernImplTest { final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null, List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10), withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true); - queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, false); + enqueueOrReplaceBroadcast(queue, airplaneRecord, 1); assertFalse(queue.isRunnable()); assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason()); @@ -481,7 +513,7 @@ public final class BroadcastQueueModernImplTest { final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, List.of(makeMockRegisteredReceiver())); - queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, false); + enqueueOrReplaceBroadcast(queue, airplaneRecord, 0); mConstants.MAX_PENDING_BROADCASTS = 128; queue.invalidateRunnableAt(); @@ -507,11 +539,11 @@ public final class BroadcastQueueModernImplTest { new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of(makeMockRegisteredReceiver())); - queue.enqueueOrReplaceBroadcast(lazyRecord, 0, false); + enqueueOrReplaceBroadcast(queue, lazyRecord, 0); assertThat(queue.getRunnableAt()).isGreaterThan(lazyRecord.enqueueTime); assertThat(queue.getRunnableAtReason()).isNotEqualTo(testRunnableAtReason); - queue.enqueueOrReplaceBroadcast(testRecord, 0, false); + enqueueOrReplaceBroadcast(queue, testRecord, 0); assertThat(queue.getRunnableAt()).isAtMost(testRecord.enqueueTime); assertThat(queue.getRunnableAtReason()).isEqualTo(testRunnableAtReason); } @@ -573,22 +605,22 @@ public final class BroadcastQueueModernImplTest { BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants, PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN)); - queue.enqueueOrReplaceBroadcast( + enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED) - .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false); - queue.enqueueOrReplaceBroadcast( - makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0, false); - queue.enqueueOrReplaceBroadcast( + .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0); + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0); + enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false); - queue.enqueueOrReplaceBroadcast( + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0); + enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED) - .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0, false); - queue.enqueueOrReplaceBroadcast( - makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0, false); - queue.enqueueOrReplaceBroadcast( + .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0); + enqueueOrReplaceBroadcast(queue, + makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0); + enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED) - .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0, false); + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0); queue.makeActiveNextPending(); assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction()); @@ -822,9 +854,6 @@ public final class BroadcastQueueModernImplTest { optionsAlarmVolumeChanged.setDeliveryGroupMatchingKey("audio", String.valueOf(AudioManager.STREAM_ALARM)); - // Halt all processing so that we get a consistent view - mHandlerThread.getLooper().getQueue().postSyncBarrier(); - mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, optionsMusicVolumeChanged)); @@ -891,9 +920,6 @@ public final class BroadcastQueueModernImplTest { String.valueOf(TEST_UID2)); optionsPackageChangedForUid.setDeliveryGroupExtrasMerger(extrasMerger); - // Halt all processing so that we get a consistent view - mHandlerThread.getLooper().getQueue().postSyncBarrier(); - mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid, optionsPackageChangedForUid)); mImpl.enqueueBroadcastLocked(makeBroadcastRecord(packageChangedForUid2, @@ -941,9 +967,6 @@ public final class BroadcastQueueModernImplTest { BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); optionsAlarmVolumeChanged.setDeliveryGroupMatchingFilter(filterAlarmVolumeChanged); - // Halt all processing so that we get a consistent view - mHandlerThread.getLooper().getQueue().postSyncBarrier(); - mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); mImpl.enqueueBroadcastLocked(makeBroadcastRecord(musicVolumeChanged, optionsMusicVolumeChanged)); @@ -990,9 +1013,6 @@ public final class BroadcastQueueModernImplTest { final Pair<Intent, BroadcastOptions> dropboxEntryBroadcast3 = createDropboxBroadcast( "TAG_A", now + 2000, 7); - // Halt all processing so that we get a consistent view - mHandlerThread.getLooper().getQueue().postSyncBarrier(); - mImpl.enqueueBroadcastLocked(makeBroadcastRecord(dropboxEntryBroadcast1.first, dropboxEntryBroadcast1.second)); mImpl.enqueueBroadcastLocked(makeBroadcastRecord(dropboxEntryBroadcast2.first, @@ -1022,9 +1042,6 @@ public final class BroadcastQueueModernImplTest { .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) .setDeliveryGroupMatchingKey(Intent.ACTION_CLOSE_SYSTEM_DIALOGS, "testing"); - // Halt all processing so that we get a consistent view - mHandlerThread.getLooper().getQueue().postSyncBarrier(); - mImpl.enqueueBroadcastLocked(makeBroadcastRecord( closeSystemDialogs1, optionsCloseSystemDialog1)); mImpl.enqueueBroadcastLocked(makeBroadcastRecord( @@ -1074,9 +1091,6 @@ public final class BroadcastQueueModernImplTest { final Intent userPresent = new Intent(Intent.ACTION_USER_PRESENT); userPresent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - // Halt all processing so that we get a consistent view - mHandlerThread.getLooper().getQueue().postSyncBarrier(); - final BroadcastRecord userPresentRecord1 = makeBroadcastRecord(userPresent); final BroadcastRecord userPresentRecord2 = makeBroadcastRecord(userPresent); @@ -1115,8 +1129,6 @@ public final class BroadcastQueueModernImplTest { makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW) )); - // Halt all processing so that we get a consistent view - mHandlerThread.getLooper().getQueue().postSyncBarrier(); mImpl.enqueueBroadcastLocked(record1); mImpl.enqueueBroadcastLocked(record2); @@ -1138,12 +1150,9 @@ public final class BroadcastQueueModernImplTest { final BroadcastOptions optionsTimeTick = BroadcastOptions.makeBasic(); optionsTimeTick.setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT); - // Halt all processing so that we get a consistent view - try (SyncBarrier b = new SyncBarrier(mHandlerThread)) { - mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); - mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); - } - mImpl.waitForIdle(LOG_WRITER_INFO); + mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); + mImpl.enqueueBroadcastLocked(makeBroadcastRecord(timeTick, optionsTimeTick)); + waitForIdle(); // Verify that there is only one delivery event reported since one of the broadcasts // should have been skipped. @@ -1164,8 +1173,8 @@ public final class BroadcastQueueModernImplTest { final Intent timeTick = new Intent(Intent.ACTION_TIME_TICK) .addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick, - List.of(makeMockRegisteredReceiver())), 0, false); + enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(timeTick, + List.of(makeMockRegisteredReceiver())), 0); assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); // Make the foreground broadcast as active. @@ -1176,15 +1185,15 @@ public final class BroadcastQueueModernImplTest { assertEquals(ProcessList.SCHED_GROUP_UNDEFINED, queue.getPreferredSchedulingGroupLocked()); final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); - queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(airplane, - List.of(makeMockRegisteredReceiver())), 0, false); + enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(airplane, + List.of(makeMockRegisteredReceiver())), 0); // Make the background broadcast as active. queue.makeActiveNextPending(); assertEquals(ProcessList.SCHED_GROUP_BACKGROUND, queue.getPreferredSchedulingGroupLocked()); - queue.enqueueOrReplaceBroadcast(makeBroadcastRecord(timeTick, - List.of(makeMockRegisteredReceiver())), 0, false); + enqueueOrReplaceBroadcast(queue, makeBroadcastRecord(timeTick, + List.of(makeMockRegisteredReceiver())), 0); // Even though the active broadcast is not a foreground one, scheduling group will be // DEFAULT since there is a foreground broadcast waiting to be delivered. assertEquals(ProcessList.SCHED_GROUP_DEFAULT, queue.getPreferredSchedulingGroupLocked()); 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 90e6a2d0f34a..7be1d7cde27f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -77,6 +77,7 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.PowerExemptionManager; import android.os.SystemClock; +import android.os.TestLooperManager; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; @@ -146,6 +147,7 @@ public class BroadcastQueueTest { private Context mContext; private HandlerThread mHandlerThread; + private TestLooperManager mLooper; private AtomicInteger mNextPid; @Mock @@ -206,6 +208,11 @@ public class BroadcastQueueTest { mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); + + // Pause all event processing until a test chooses to resume + mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation() + .acquireLooperManager(mHandlerThread.getLooper())); + mNextPid = new AtomicInteger(100); LocalServices.removeServiceForTest(DropBoxManagerInternal.class); @@ -360,25 +367,6 @@ public class BroadcastQueueTest { } } - /** - * Helper that leverages try-with-resources to pause dispatch of - * {@link #mHandlerThread} until released. - */ - static class SyncBarrier implements AutoCloseable { - private final int mToken; - private HandlerThread mThread; - - SyncBarrier(HandlerThread thread) { - mThread = thread; - mToken = mThread.getLooper().getQueue().postSyncBarrier(); - } - - @Override - public void close() throws Exception { - mThread.getLooper().getQueue().removeSyncBarrier(mToken); - } - } - private enum ProcessStartBehavior { /** Process starts successfully */ SUCCESS, @@ -671,8 +659,15 @@ public class BroadcastQueueTest { } } + /** + * Un-pause our handler to process pending events, wait for our queue to go + * idle, and then re-pause the handler. + */ private void waitForIdle() throws Exception { + mLooper.release(); mQueue.waitForIdle(LOG_WRITER_INFO); + mLooper = Objects.requireNonNull(InstrumentationRegistry.getInstrumentation() + .acquireLooperManager(mHandlerThread.getLooper())); } private void verifyScheduleReceiver(ProcessRecord app, Intent intent) throws Exception { @@ -1156,23 +1151,21 @@ public class BroadcastQueueTest { final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN); final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); - try (SyncBarrier b = new SyncBarrier(mHandlerThread)) { - enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>( - List.of(makeRegisteredReceiver(receiverApp), - makeManifestReceiver(PACKAGE_GREEN, CLASS_RED), - makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), - makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE))))); - - synchronized (mAms) { - mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_GREEN, Set.of(CLASS_GREEN), - UserHandle.USER_SYSTEM); - - // Also try clearing out other unrelated things that should leave - // the final receiver intact - mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_RED, null, - UserHandle.USER_SYSTEM); - mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST); - } + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>( + List.of(makeRegisteredReceiver(receiverApp), + makeManifestReceiver(PACKAGE_GREEN, CLASS_RED), + makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_GREEN, CLASS_BLUE))))); + + synchronized (mAms) { + mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_GREEN, Set.of(CLASS_GREEN), + UserHandle.USER_SYSTEM); + + // Also try clearing out other unrelated things that should leave + // the final receiver intact + mQueue.cleanupDisabledPackageReceiversLocked(PACKAGE_RED, null, + UserHandle.USER_SYSTEM); + mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST); // To maximize test coverage, dump current state; we're not worried // about the actual output, just that we don't crash @@ -1200,21 +1193,19 @@ public class BroadcastQueueTest { final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); final Intent timeZone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); - try (SyncBarrier b = new SyncBarrier(mHandlerThread)) { - enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, USER_GUEST, new ArrayList<>( - List.of(makeRegisteredReceiver(callerApp), - makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST), - makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST), - makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST))))); - enqueueBroadcast(makeBroadcastRecord(timeZone, callerApp, USER_GUEST, new ArrayList<>( - List.of(makeRegisteredReceiver(callerApp), - makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST), - makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST), - makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST))))); - - synchronized (mAms) { - mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST); - } + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, USER_GUEST, new ArrayList<>( + List.of(makeRegisteredReceiver(callerApp), + makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST), + makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST), + makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST))))); + enqueueBroadcast(makeBroadcastRecord(timeZone, callerApp, USER_GUEST, new ArrayList<>( + List.of(makeRegisteredReceiver(callerApp), + makeManifestReceiver(PACKAGE_GREEN, CLASS_RED, USER_GUEST), + makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN, USER_GUEST), + makeManifestReceiver(PACKAGE_YELLOW, CLASS_BLUE, USER_GUEST))))); + + synchronized (mAms) { + mQueue.cleanupDisabledPackageReceiversLocked(null, null, USER_GUEST); } waitForIdle(); @@ -1240,15 +1231,12 @@ public class BroadcastQueueTest { final ProcessRecord oldApp = makeActiveProcessRecord(PACKAGE_GREEN); final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); - try (SyncBarrier b = new SyncBarrier(mHandlerThread)) { - enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>( - List.of(makeRegisteredReceiver(oldApp), - makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))))); - - synchronized (mAms) { - oldApp.killLocked(TAG, 42, false); - mQueue.onApplicationCleanupLocked(oldApp); - } + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, new ArrayList<>( + List.of(makeRegisteredReceiver(oldApp), + makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN))))); + synchronized (mAms) { + oldApp.killLocked(TAG, 42, false); + mQueue.onApplicationCleanupLocked(oldApp); } waitForIdle(); @@ -1621,17 +1609,14 @@ public class BroadcastQueueTest { final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); - try (SyncBarrier b = new SyncBarrier(mHandlerThread)) { - enqueueBroadcast(makeBroadcastRecord(timezone, callerApp, - List.of(makeRegisteredReceiver(receiverBlueApp, 10), - makeRegisteredReceiver(receiverGreenApp, 10), - makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), - makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), - makeRegisteredReceiver(receiverYellowApp, -10)))); - enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, - List.of(makeRegisteredReceiver(receiverBlueApp)))); - } - + enqueueBroadcast(makeBroadcastRecord(timezone, callerApp, + List.of(makeRegisteredReceiver(receiverBlueApp, 10), + makeRegisteredReceiver(receiverGreenApp, 10), + makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), + makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), + makeRegisteredReceiver(receiverYellowApp, -10)))); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, + List.of(makeRegisteredReceiver(receiverBlueApp)))); waitForIdle(); // Ignore the final foreground broadcast @@ -1745,17 +1730,15 @@ public class BroadcastQueueTest { final IIntentReceiver resultToFirst = mock(IIntentReceiver.class); final IIntentReceiver resultToSecond = mock(IIntentReceiver.class); - try (SyncBarrier b = new SyncBarrier(mHandlerThread)) { - enqueueBroadcast(makeOrderedBroadcastRecord(timezoneFirst, callerApp, - List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), - makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)), - resultToFirst, null)); - enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, - List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_RED)))); - enqueueBroadcast(makeOrderedBroadcastRecord(timezoneSecond, callerApp, - List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)), - resultToSecond, null)); - } + enqueueBroadcast(makeOrderedBroadcastRecord(timezoneFirst, callerApp, + List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), + makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)), + resultToFirst, null)); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, + List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_RED)))); + enqueueBroadcast(makeOrderedBroadcastRecord(timezoneSecond, callerApp, + List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_GREEN)), + resultToSecond, null)); waitForIdle(); final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE, @@ -1836,14 +1819,12 @@ public class BroadcastQueueTest { timeTickFirst.putExtra(Intent.EXTRA_INDEX, "third"); timeTickThird.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - try (SyncBarrier b = new SyncBarrier(mHandlerThread)) { - enqueueBroadcast(makeBroadcastRecord(timeTickFirst, callerApp, - List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)))); - enqueueBroadcast(makeBroadcastRecord(timeTickSecond, callerApp, - List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)))); - enqueueBroadcast(makeBroadcastRecord(timeTickThird, callerApp, - List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)))); - } + enqueueBroadcast(makeBroadcastRecord(timeTickFirst, callerApp, + List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)))); + enqueueBroadcast(makeBroadcastRecord(timeTickSecond, callerApp, + List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)))); + enqueueBroadcast(makeBroadcastRecord(timeTickThird, callerApp, + List.of(makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE)))); waitForIdle(); final IApplicationThread blueThread = mAms.getProcessRecordLocked(PACKAGE_BLUE, @@ -1878,26 +1859,26 @@ public class BroadcastQueueTest { assertTrue(mQueue.isIdleLocked()); assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst)); - try (SyncBarrier b = new SyncBarrier(mHandlerThread)) { - final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); - enqueueBroadcast(makeBroadcastRecord(timezone, callerApp, - List.of(makeRegisteredReceiver(receiverApp)))); - afterFirst = SystemClock.uptimeMillis(); - - assertFalse(mQueue.isIdleLocked()); - assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst)); - assertFalse(mQueue.isBeyondBarrierLocked(afterFirst)); - - final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); - enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, - List.of(makeRegisteredReceiver(receiverApp)))); - afterSecond = SystemClock.uptimeMillis() + 10; - - assertFalse(mQueue.isIdleLocked()); - assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst)); - assertFalse(mQueue.isBeyondBarrierLocked(afterFirst)); - assertFalse(mQueue.isBeyondBarrierLocked(afterSecond)); - } + final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(timezone, callerApp, + List.of(makeRegisteredReceiver(receiverApp)))); + afterFirst = SystemClock.uptimeMillis(); + + assertFalse(mQueue.isIdleLocked()); + assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst)); + assertFalse(mQueue.isBeyondBarrierLocked(afterFirst)); + + final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, + List.of(makeRegisteredReceiver(receiverApp)))); + afterSecond = SystemClock.uptimeMillis() + 10; + + assertFalse(mQueue.isIdleLocked()); + assertTrue(mQueue.isBeyondBarrierLocked(beforeFirst)); + assertFalse(mQueue.isBeyondBarrierLocked(afterFirst)); + assertFalse(mQueue.isBeyondBarrierLocked(afterSecond)); + + mLooper.release(); mQueue.waitForBarrier(LOG_WRITER_INFO); assertTrue(mQueue.isBeyondBarrierLocked(afterFirst)); @@ -2048,25 +2029,23 @@ public class BroadcastQueueTest { final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE); final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); - try (SyncBarrier b = new SyncBarrier(mHandlerThread)) { - final Object greenReceiver = makeRegisteredReceiver(receiverGreenApp); - final Object blueReceiver = makeRegisteredReceiver(receiverBlueApp); - final Object yellowReceiver = makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW); - final Object orangeReceiver = makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE); - enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, - List.of(greenReceiver, blueReceiver, yellowReceiver, orangeReceiver))); - - doAnswer(invocation -> { - final BroadcastRecord r = invocation.getArgument(0); - final Object o = invocation.getArgument(1); - if (airplane.getAction().equals(r.intent.getAction()) - && (isReceiverEquals(o, greenReceiver) - || isReceiverEquals(o, orangeReceiver))) { - return "test skipped receiver"; - } - return null; - }).when(mSkipPolicy).shouldSkipMessage(any(BroadcastRecord.class), any()); - } + final Object greenReceiver = makeRegisteredReceiver(receiverGreenApp); + final Object blueReceiver = makeRegisteredReceiver(receiverBlueApp); + final Object yellowReceiver = makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW); + final Object orangeReceiver = makeManifestReceiver(PACKAGE_ORANGE, CLASS_ORANGE); + enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, + List.of(greenReceiver, blueReceiver, yellowReceiver, orangeReceiver))); + + doAnswer(invocation -> { + final BroadcastRecord r = invocation.getArgument(0); + final Object o = invocation.getArgument(1); + if (airplane.getAction().equals(r.intent.getAction()) + && (isReceiverEquals(o, greenReceiver) + || isReceiverEquals(o, orangeReceiver))) { + return "test skipped receiver"; + } + return null; + }).when(mSkipPolicy).shouldSkipMessage(any(BroadcastRecord.class), any()); waitForIdle(); // Verify that only blue and yellow receiver apps received the broadcast. diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java index 201da359aeb6..9f685b479d47 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/BackupAndRestoreFeatureFlagsTest.java @@ -104,4 +104,24 @@ public class BackupAndRestoreFeatureFlagsTest { assertThat(BackupAndRestoreFeatureFlags.getFullBackupUtilsRouteBufferSizeBytes()) .isEqualTo(5678); } + + @Test + public void getUnifiedRestoreContinueAfterTransportFailureInKvRestore_notSet_returnsDefault() { + assertThat( + BackupAndRestoreFeatureFlags + .getUnifiedRestoreContinueAfterTransportFailureInKvRestore()) + .isEqualTo(true); + } + + @Test + public void getUnifiedRestoreContinueAfterTransportFailureInKvRestore_set_returnsSetValue() { + DeviceConfig.setProperty(/*namespace=*/ "backup_and_restore", + /*name=*/ "unified_restore_continue_after_transport_failure_in_kv_restore", + /*value=*/ "false", /*makeDefault=*/ false); + + assertThat( + BackupAndRestoreFeatureFlags + .getUnifiedRestoreContinueAfterTransportFailureInKvRestore()) + .isEqualTo(false); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java index 017c93975286..c84797febcfd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/restore/PerformUnifiedRestoreTaskTest.java @@ -25,20 +25,33 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; -import androidx.test.InstrumentationRegistry; -import androidx.test.runner.AndroidJUnit4; - import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; +import android.app.backup.BackupTransport; +import android.content.Context; +import android.content.pm.PackageInfo; +import android.os.Message; +import android.os.RemoteException; import android.platform.test.annotations.Presubmit; +import android.provider.DeviceConfig; + +import androidx.test.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; +import com.android.modules.utils.testing.TestableDeviceConfig; import com.android.server.backup.UserBackupManagerService; +import com.android.server.backup.internal.BackupHandler; +import com.android.server.backup.transport.BackupTransportClient; +import com.android.server.backup.transport.TransportConnection; +import com.android.server.backup.transport.TransportNotAvailableException; import org.junit.Before; +import org.junit.Rule; 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 org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -62,9 +75,14 @@ public class PerformUnifiedRestoreTaskTest { private static final String SYSTEM_PACKAGE_NAME = "android"; private static final String NON_SYSTEM_PACKAGE_NAME = "package"; - @Mock private BackupDataInput mBackupDataInput; - @Mock private BackupDataOutput mBackupDataOutput; - @Mock private UserBackupManagerService mBackupManagerService; + @Mock + private BackupDataInput mBackupDataInput; + @Mock + private BackupDataOutput mBackupDataOutput; + @Mock + private UserBackupManagerService mBackupManagerService; + @Mock + private TransportConnection mTransportConnection; private Set<String> mExcludedkeys = new HashSet<>(); private Map<String, String> mBackupData = new HashMap<>(); @@ -74,12 +92,20 @@ public class PerformUnifiedRestoreTaskTest { private Set<String> mBackupDataDump; private PerformUnifiedRestoreTask mRestoreTask; + @Rule + public TestableDeviceConfig.TestableDeviceConfigRule + mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule(); + + private Context mContext; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); populateTestData(); + mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + mBackupDataSource = new ArrayDeque<>(mBackupData.keySet()); when(mBackupDataInput.readNextHeader()).then(new Answer<Boolean>() { @Override @@ -106,7 +132,7 @@ public class PerformUnifiedRestoreTaskTest { } }); - mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService); + mRestoreTask = new PerformUnifiedRestoreTask(mBackupManagerService, mTransportConnection); } private void populateTestData() { @@ -179,4 +205,64 @@ public class PerformUnifiedRestoreTaskTest { assertTrue(mRestoreTask.shouldStageBackupData(SYSTEM_PACKAGE_NAME)); } + + @Test + public void testFailedKeyValueRestore_continueAfterFeatureEnabled_nextStateIsRunningQueue() + throws TransportNotAvailableException, RemoteException { + DeviceConfig.setProperty( + "backup_and_restore", + "unified_restore_continue_after_transport_failure_in_kv_restore", + "true", + false); + + setupForRestoreKeyValueState(BackupTransport.TRANSPORT_ERROR); + + mRestoreTask.setCurrentUnifiedRestoreStateForTesting(UnifiedRestoreState.RESTORE_KEYVALUE); + mRestoreTask.setStateDirForTesting(mContext.getCacheDir()); + + PackageInfo testPackageInfo = new PackageInfo(); + testPackageInfo.packageName = "test.package.name"; + mRestoreTask.initiateOneRestoreForTesting(testPackageInfo, 0L); + assertTrue( + mRestoreTask.getCurrentUnifiedRestoreStateForTesting() + == UnifiedRestoreState.RUNNING_QUEUE); + } + + @Test + public void testFailedKeyValueRestore_continueAfterFeatureDisabled_nextStateIsFinal() + throws RemoteException, TransportNotAvailableException { + DeviceConfig.setProperty( + "backup_and_restore", + "unified_restore_continue_after_transport_failure_in_kv_restore", + "false", + false); + + setupForRestoreKeyValueState(BackupTransport.TRANSPORT_ERROR); + + mRestoreTask.setCurrentUnifiedRestoreStateForTesting(UnifiedRestoreState.RESTORE_KEYVALUE); + mRestoreTask.setStateDirForTesting(mContext.getCacheDir()); + + PackageInfo testPackageInfo = new PackageInfo(); + testPackageInfo.packageName = "test.package.name"; + mRestoreTask.initiateOneRestoreForTesting(testPackageInfo, 0L); + assertTrue( + mRestoreTask.getCurrentUnifiedRestoreStateForTesting() + == UnifiedRestoreState.FINAL); + } + + private void setupForRestoreKeyValueState(int transportStatus) + throws RemoteException, TransportNotAvailableException { + // Mock BackupHandler to do nothing when executeNextState() is called + BackupHandler backupHandler = Mockito.mock(BackupHandler.class); + when(backupHandler.obtainMessage(anyInt(), any())).thenReturn(new Message()); + when(backupHandler.sendMessage(any())).thenReturn(true); + + // Return cache directory for any bookkeeping or maintaining persistent state. + when(mBackupManagerService.getDataDir()).thenReturn(mContext.getCacheDir()); + when(mBackupManagerService.getBackupHandler()).thenReturn(backupHandler); + + BackupTransportClient transport = Mockito.mock(BackupTransportClient.class); + when(transport.getRestoreData(any())).thenReturn(transportStatus); + when(mTransportConnection.connectOrThrow(any())).thenReturn(transport); + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java index 45fefe477f8a..ca857f121624 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java @@ -38,9 +38,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; -import android.content.ContentResolver; import android.content.Context; -import android.content.ContextWrapper; import android.content.res.Resources; import android.hardware.Sensor; import android.hardware.SensorEventListener; @@ -51,18 +49,18 @@ import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.SystemProperties; -import android.os.UserHandle; import android.os.test.TestLooper; import android.provider.Settings; +import android.testing.TestableContext; import android.util.FloatProperty; import android.view.Display; import android.view.DisplayInfo; -import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; -import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.server.ExtendedMockitoRule; import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; @@ -74,12 +72,12 @@ import com.android.server.testutils.OffsettableClock; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; @@ -97,11 +95,9 @@ public final class DisplayPowerController2Test { private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789"; private static final float PROX_SENSOR_MAX_RANGE = 5; - private MockitoSession mSession; private OffsettableClock mClock; private TestLooper mTestLooper; private Handler mHandler; - private Context mContextSpy; private DisplayPowerControllerHolder mHolder; private Sensor mProxSensor; @@ -118,40 +114,44 @@ public final class DisplayPowerController2Test { @Mock private PowerManager mPowerManagerMock; @Mock - private Resources mResourcesMock; - @Mock private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock; @Captor private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor; + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + + @Rule + public final ExtendedMockitoRule mExtendedMockitoRule = + new ExtendedMockitoRule.Builder(this) + .setStrictness(Strictness.LENIENT) + .spyStatic(SystemProperties.class) + .spyStatic(BatteryStatsService.class) + .build(); + @Before public void setUp() throws Exception { - mSession = ExtendedMockito.mockitoSession() - .initMocks(this) - .strictness(Strictness.LENIENT) - .spyStatic(SystemProperties.class) - .spyStatic(LocalServices.class) - .spyStatic(BatteryStatsService.class) - .spyStatic(Settings.System.class) - .startMocking(); - mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); mHandler = new Handler(mTestLooper.getLooper()); + + // Put the system into manual brightness by default, just to minimize unexpected events and + // have a consistent starting state + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); + addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock); + addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class, + mCdsiMock); - when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock); - when(mContextSpy.getResources()).thenReturn(mResourcesMock); + mContext.addMockSystemService(PowerManager.class, mPowerManagerMock); doAnswer((Answer<Void>) invocationOnMock -> null).when(() -> SystemProperties.set(anyString(), any())); - doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock -> - mCdsiMock).when(() -> LocalServices.getService( - ColorDisplayService.ColorDisplayServiceInternal.class)); doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService); - doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() -> - Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt())); setUpSensors(); mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); @@ -159,8 +159,8 @@ public final class DisplayPowerController2Test { @After public void tearDown() { - mSession.finishMocking(); LocalServices.removeServiceForTest(WindowManagerPolicy.class); + LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class); } @Test @@ -421,11 +421,9 @@ public final class DisplayPowerController2Test { @Test public void testDisplayBrightnessFollowers_AutomaticBrightness() { - doAnswer((Answer<Integer>) invocationOnMock -> - Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) - .when(() -> Settings.System.getIntForUser(any(ContentResolver.class), - eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), - eq(UserHandle.USER_CURRENT))); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); final float brightness = 0.4f; final float nits = 300; final float ambientLux = 3000; @@ -436,7 +434,7 @@ public final class DisplayPowerController2Test { when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits); when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux); when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); - DisplayPowerController followerDpc = mock(DisplayPowerController.class); + DisplayPowerController2 followerDpc = mock(DisplayPowerController2.class); mHolder.dpc.addDisplayBrightnessFollower(followerDpc); DisplayPowerRequest dpr = new DisplayPowerRequest(); @@ -542,11 +540,9 @@ public final class DisplayPowerController2Test { @Test public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() { - doAnswer((Answer<Integer>) invocationOnMock -> - Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) - .when(() -> Settings.System.getIntForUser(any(ContentResolver.class), - eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), - eq(UserHandle.USER_CURRENT))); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); DisplayPowerRequest dpr = new DisplayPowerRequest(); dpr.policy = DisplayPowerRequest.POLICY_OFF; @@ -577,17 +573,14 @@ public final class DisplayPowerController2Test { @Test public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() { - doAnswer((Answer<Integer>) invocationOnMock -> - Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) - .when(() -> Settings.System.getIntForUser(any(ContentResolver.class), - eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), - eq(UserHandle.USER_CURRENT))); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); DisplayPowerRequest dpr = new DisplayPowerRequest(); dpr.policy = DisplayPowerRequest.POLICY_DOZE; - when(mResourcesMock.getBoolean( - com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing)) - .thenReturn(true); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true); mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); advanceTime(1); // Run updatePowerState @@ -615,12 +608,7 @@ public final class DisplayPowerController2Test { @Test public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() { - doAnswer((Answer<Integer>) invocationOnMock -> - Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) - .when(() -> Settings.System.getIntForUser(any(ContentResolver.class), - eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), - eq(UserHandle.USER_CURRENT))); - + // Tests are set up with manual brightness by default, so no need to set it here. DisplayPowerRequest dpr = new DisplayPowerRequest(); dpr.policy = DisplayPowerRequest.POLICY_OFF; mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); @@ -632,11 +620,9 @@ public final class DisplayPowerController2Test { @Test public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() { - doAnswer((Answer<Integer>) invocationOnMock -> - Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) - .when(() -> Settings.System.getIntForUser(any(ContentResolver.class), - eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), - eq(UserHandle.USER_CURRENT))); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false); DisplayPowerRequest dpr = new DisplayPowerRequest(); @@ -690,9 +676,9 @@ public final class DisplayPowerController2Test { public void testBrightnessNitsPersistWhenDisplayDeviceChanges() { float brightness = 0.3f; float nits = 500; - when(mResourcesMock.getBoolean( - com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay)) - .thenReturn(true); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay, + true); mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits); @@ -753,7 +739,7 @@ public final class DisplayPowerController2Test { any(HysteresisLevels.class), any(HysteresisLevels.class), any(HysteresisLevels.class), - eq(mContextSpy), + eq(mContext), any(HighBrightnessModeController.class), any(BrightnessThrottler.class), isNull(), @@ -862,7 +848,7 @@ public final class DisplayPowerController2Test { setUpDisplay(displayId, uniqueId, display, device, config, isEnabled); final DisplayPowerController2 dpc = new DisplayPowerController2( - mContextSpy, injector, mDisplayPowerCallbacksMock, mHandler, + mContext, injector, mDisplayPowerCallbacksMock, mHandler, mSensorManagerMock, mDisplayBlankerMock, display, mBrightnessTrackerMock, brightnessSetting, () -> {}, hbmMetadata, /* bootCompleted= */ false); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java index d9133a441aff..0b97c5cb6ca1 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -38,9 +38,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.when; -import android.content.ContentResolver; import android.content.Context; -import android.content.ContextWrapper; import android.content.res.Resources; import android.hardware.Sensor; import android.hardware.SensorEventListener; @@ -51,18 +49,18 @@ import android.os.Handler; import android.os.Looper; import android.os.PowerManager; import android.os.SystemProperties; -import android.os.UserHandle; import android.os.test.TestLooper; import android.provider.Settings; +import android.testing.TestableContext; import android.util.FloatProperty; import android.view.Display; import android.view.DisplayInfo; -import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; +import androidx.test.platform.app.InstrumentationRegistry; -import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.server.ExtendedMockitoRule; import com.android.server.LocalServices; import com.android.server.am.BatteryStatsService; import com.android.server.display.RampAnimator.DualRampAnimator; @@ -75,12 +73,12 @@ import com.android.server.testutils.OffsettableClock; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; -import org.mockito.MockitoSession; import org.mockito.quality.Strictness; import org.mockito.stubbing.Answer; @@ -98,11 +96,9 @@ public final class DisplayPowerControllerTest { private static final String SECOND_FOLLOWER_UNIQUE_DISPLAY_ID = "unique_id_789"; private static final float PROX_SENSOR_MAX_RANGE = 5; - private MockitoSession mSession; private OffsettableClock mClock; private TestLooper mTestLooper; private Handler mHandler; - private Context mContextSpy; private DisplayPowerControllerHolder mHolder; private Sensor mProxSensor; @@ -119,41 +115,45 @@ public final class DisplayPowerControllerTest { @Mock private PowerManager mPowerManagerMock; @Mock - private Resources mResourcesMock; - @Mock private ColorDisplayService.ColorDisplayServiceInternal mCdsiMock; @Captor private ArgumentCaptor<SensorEventListener> mSensorEventListenerCaptor; + @Rule + public final TestableContext mContext = new TestableContext( + InstrumentationRegistry.getInstrumentation().getContext()); + + @Rule + public final ExtendedMockitoRule mExtendedMockitoRule = + new ExtendedMockitoRule.Builder(this) + .setStrictness(Strictness.LENIENT) + .spyStatic(SystemProperties.class) + .spyStatic(BatteryStatsService.class) + .build(); + @Before public void setUp() throws Exception { - mSession = ExtendedMockito.mockitoSession() - .initMocks(this) - .strictness(Strictness.LENIENT) - .spyStatic(SystemProperties.class) - .spyStatic(LocalServices.class) - .spyStatic(BatteryStatsService.class) - .spyStatic(Settings.System.class) - .startMocking(); - mContextSpy = spy(new ContextWrapper(ApplicationProvider.getApplicationContext())); mClock = new OffsettableClock.Stopped(); mTestLooper = new TestLooper(mClock::now); mHandler = new Handler(mTestLooper.getLooper()); + // Put the system into manual brightness by default, just to minimize unexpected events and + // have a consistent starting state + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL); + + addLocalServiceMock(WindowManagerPolicy.class, mWindowManagerPolicyMock); + addLocalServiceMock(ColorDisplayService.ColorDisplayServiceInternal.class, + mCdsiMock); - when(mContextSpy.getSystemService(eq(PowerManager.class))).thenReturn(mPowerManagerMock); - when(mContextSpy.getResources()).thenReturn(mResourcesMock); + mContext.addMockSystemService(PowerManager.class, mPowerManagerMock); doAnswer((Answer<Void>) invocationOnMock -> null).when(() -> SystemProperties.set(anyString(), any())); - doAnswer((Answer<ColorDisplayService.ColorDisplayServiceInternal>) invocationOnMock -> - mCdsiMock).when(() -> LocalServices.getService( - ColorDisplayService.ColorDisplayServiceInternal.class)); doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService); - doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() -> - Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt())); setUpSensors(); mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); @@ -161,8 +161,8 @@ public final class DisplayPowerControllerTest { @After public void tearDown() { - mSession.finishMocking(); LocalServices.removeServiceForTest(WindowManagerPolicy.class); + LocalServices.removeServiceForTest(ColorDisplayService.ColorDisplayServiceInternal.class); } @Test @@ -425,11 +425,9 @@ public final class DisplayPowerControllerTest { @Test public void testDisplayBrightnessFollowers_AutomaticBrightness() { - doAnswer((Answer<Integer>) invocationOnMock -> - Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) - .when(() -> Settings.System.getIntForUser(any(ContentResolver.class), - eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), - eq(UserHandle.USER_CURRENT))); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); final float brightness = 0.4f; final float nits = 300; final float ambientLux = 3000; @@ -547,11 +545,9 @@ public final class DisplayPowerControllerTest { @Test public void testSetScreenOffBrightnessSensorEnabled_DisplayIsOff() { - doAnswer((Answer<Integer>) invocationOnMock -> - Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) - .when(() -> Settings.System.getIntForUser(any(ContentResolver.class), - eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), - eq(UserHandle.USER_CURRENT))); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); DisplayPowerRequest dpr = new DisplayPowerRequest(); dpr.policy = DisplayPowerRequest.POLICY_OFF; @@ -582,17 +578,14 @@ public final class DisplayPowerControllerTest { @Test public void testSetScreenOffBrightnessSensorEnabled_DisplayIsInDoze() { - doAnswer((Answer<Integer>) invocationOnMock -> - Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) - .when(() -> Settings.System.getIntForUser(any(ContentResolver.class), - eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), - eq(UserHandle.USER_CURRENT))); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); DisplayPowerRequest dpr = new DisplayPowerRequest(); dpr.policy = DisplayPowerRequest.POLICY_DOZE; - when(mResourcesMock.getBoolean( - com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing)) - .thenReturn(true); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_allowAutoBrightnessWhileDozing, true); mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); advanceTime(1); // Run updatePowerState @@ -620,12 +613,7 @@ public final class DisplayPowerControllerTest { @Test public void testSetScreenOffBrightnessSensorDisabled_AutoBrightnessIsDisabled() { - doAnswer((Answer<Integer>) invocationOnMock -> - Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL) - .when(() -> Settings.System.getIntForUser(any(ContentResolver.class), - eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), - eq(UserHandle.USER_CURRENT))); - + // Tests are set up with manual brightness by default, so no need to set it here. DisplayPowerRequest dpr = new DisplayPowerRequest(); dpr.policy = DisplayPowerRequest.POLICY_OFF; mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); @@ -637,11 +625,10 @@ public final class DisplayPowerControllerTest { @Test public void testSetScreenOffBrightnessSensorDisabled_DisplayIsDisabled() { - doAnswer((Answer<Integer>) invocationOnMock -> - Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) - .when(() -> Settings.System.getIntForUser(any(ContentResolver.class), - eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), - eq(UserHandle.USER_CURRENT))); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID, /* isEnabled= */ false); DisplayPowerRequest dpr = new DisplayPowerRequest(); @@ -695,9 +682,10 @@ public final class DisplayPowerControllerTest { public void testBrightnessNitsPersistWhenDisplayDeviceChanges() { float brightness = 0.3f; float nits = 500; - when(mResourcesMock.getBoolean( - com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay)) - .thenReturn(true); + mContext.getOrCreateTestableResources().addOverride( + com.android.internal.R.bool.config_persistBrightnessNitsForDefaultDisplay, + true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits); @@ -758,7 +746,7 @@ public final class DisplayPowerControllerTest { any(HysteresisLevels.class), any(HysteresisLevels.class), any(HysteresisLevels.class), - eq(mContextSpy), + eq(mContext), any(HighBrightnessModeController.class), any(BrightnessThrottler.class), isNull(), @@ -866,7 +854,7 @@ public final class DisplayPowerControllerTest { setUpDisplay(displayId, uniqueId, display, device, config, isEnabled); final DisplayPowerController dpc = new DisplayPowerController( - mContextSpy, injector, mDisplayPowerCallbacksMock, mHandler, + mContext, injector, mDisplayPowerCallbacksMock, mHandler, mSensorManagerMock, mDisplayBlankerMock, display, mBrightnessTrackerMock, brightnessSetting, () -> {}, hbmMetadata, /* bootCompleted= */ false); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java index df2f59a1cefc..e24354f26d8b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobNotificationCoordinatorTest.java @@ -149,7 +149,8 @@ public class JobNotificationCoordinatorTest { .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(), eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid))); - coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED); + coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED, + jsc.getRunningJobLocked()); verify(mNotificationManagerInternal, never()) .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(), anyInt(), anyInt()); @@ -170,7 +171,8 @@ public class JobNotificationCoordinatorTest { .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(), eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid))); - coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED); + coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_UNDEFINED, + jsc.getRunningJobLocked()); verify(mNotificationManagerInternal) .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(), eq(notificationId), eq(UserHandle.getUserId(uid))); @@ -292,7 +294,8 @@ public class JobNotificationCoordinatorTest { eq(notificationId2), eq(notification2), eq(UserHandle.getUserId(uid))); // Remove the first job. Only the first notification should be removed. - coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED); + coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED, + jsc1.getRunningJobLocked()); inOrder.verify(mNotificationManagerInternal) .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(), eq(notificationId1), eq(UserHandle.getUserId(uid))); @@ -300,7 +303,8 @@ public class JobNotificationCoordinatorTest { .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(), eq(notificationId2), anyInt()); - coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED); + coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED, + jsc2.getRunningJobLocked()); inOrder.verify(mNotificationManagerInternal) .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(), eq(notificationId2), eq(UserHandle.getUserId(uid))); @@ -335,12 +339,14 @@ public class JobNotificationCoordinatorTest { eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid))); // Remove the first job. The notification shouldn't be touched because of the 2nd job. - coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED); + coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED, + jsc1.getRunningJobLocked()); inOrder.verify(mNotificationManagerInternal, never()) .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(), anyInt(), anyInt()); - coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED); + coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED, + jsc2.getRunningJobLocked()); inOrder.verify(mNotificationManagerInternal) .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(), eq(notificationId), eq(UserHandle.getUserId(uid))); @@ -376,7 +382,8 @@ public class JobNotificationCoordinatorTest { eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid2))); // Remove the first job. Only the first notification should be removed. - coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED); + coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED, + jsc1.getRunningJobLocked()); inOrder.verify(mNotificationManagerInternal) .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid1), eq(pid), any(), eq(notificationId), eq(UserHandle.getUserId(uid1))); @@ -384,7 +391,8 @@ public class JobNotificationCoordinatorTest { .cancelNotification(anyString(), anyString(), eq(uid2), anyInt(), any(), anyInt(), anyInt()); - coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED); + coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED, + jsc2.getRunningJobLocked()); inOrder.verify(mNotificationManagerInternal) .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid2), eq(pid), any(), eq(notificationId), eq(UserHandle.getUserId(uid2))); @@ -421,7 +429,8 @@ public class JobNotificationCoordinatorTest { eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid))); // Remove the first job. Only the first notification should be removed. - coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED); + coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_UNDEFINED, + jsc1.getRunningJobLocked()); inOrder.verify(mNotificationManagerInternal) .cancelNotification(eq(pkg1), eq(pkg1), eq(uid), eq(pid), any(), eq(notificationId), eq(UserHandle.getUserId(uid))); @@ -429,7 +438,8 @@ public class JobNotificationCoordinatorTest { .cancelNotification(anyString(), anyString(), eq(uid), anyInt(), any(), anyInt(), anyInt()); - coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED); + coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_UNDEFINED, + jsc2.getRunningJobLocked()); inOrder.verify(mNotificationManagerInternal) .cancelNotification(eq(pkg2), eq(pkg2), eq(uid), eq(pid), any(), eq(notificationId), eq(UserHandle.getUserId(uid))); @@ -450,7 +460,8 @@ public class JobNotificationCoordinatorTest { .enqueueNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(), eq(notificationId), eq(notification), eq(UserHandle.getUserId(uid))); - coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_USER); + coordinator.removeNotificationAssociation(jsc, JobParameters.STOP_REASON_USER, + jsc.getRunningJobLocked()); verify(mNotificationManagerInternal) .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(), eq(notificationId), eq(UserHandle.getUserId(uid))); @@ -485,12 +496,14 @@ public class JobNotificationCoordinatorTest { eq(notificationId), eq(notification2), eq(UserHandle.getUserId(uid))); // Remove the first job. The notification shouldn't be touched because of the 2nd job. - coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_USER); + coordinator.removeNotificationAssociation(jsc1, JobParameters.STOP_REASON_USER, + jsc1.getRunningJobLocked()); inOrder.verify(mNotificationManagerInternal, never()) .cancelNotification(anyString(), anyString(), anyInt(), anyInt(), any(), anyInt(), anyInt()); - coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_USER); + coordinator.removeNotificationAssociation(jsc2, JobParameters.STOP_REASON_USER, + jsc2.getRunningJobLocked()); inOrder.verify(mNotificationManagerInternal) .cancelNotification(eq(TEST_PACKAGE), eq(TEST_PACKAGE), eq(uid), eq(pid), any(), eq(notificationId), eq(UserHandle.getUserId(uid))); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java index 02fdfadb2d8a..754f409b3966 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java @@ -276,9 +276,9 @@ public class ThermalStatusRestrictionTest { assertFalse(mThermalStatusRestriction.isJobRestricted(ejRunning)); assertTrue(mThermalStatusRestriction.isJobRestricted(ejRunningLong)); assertFalse(mThermalStatusRestriction.isJobRestricted(ui)); - assertTrue(mThermalStatusRestriction.isJobRestricted(uiRetried)); + assertFalse(mThermalStatusRestriction.isJobRestricted(uiRetried)); assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunning)); - assertTrue(mThermalStatusRestriction.isJobRestricted(uiRunningLong)); + assertFalse(mThermalStatusRestriction.isJobRestricted(uiRunningLong)); mStatusChangedListener.onThermalStatusChanged(THERMAL_STATUS_SEVERE); diff --git a/services/tests/servicestests/res/xml/irq_device_map_3.xml b/services/tests/servicestests/res/xml/irq_device_map_3.xml index 1d2a7d37d613..7e2529aa0090 100644 --- a/services/tests/servicestests/res/xml/irq_device_map_3.xml +++ b/services/tests/servicestests/res/xml/irq_device_map_3.xml @@ -23,4 +23,7 @@ <device name="test.wifi.device"> <subsystem>Wifi</subsystem> </device> + <device name="test.sound_trigger.device"> + <subsystem>Sound_trigger</subsystem> + </device> </irq-device-map>
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java index 7468901a16af..d5d06d3b4eb8 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/face/aidl/FaceAuthenticationClientTest.java @@ -16,6 +16,8 @@ package com.android.server.biometrics.sensors.face.aidl; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT; +import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -148,6 +150,28 @@ public class FaceAuthenticationClientTest { } @Test + public void testLockoutEndsOperation() throws RemoteException { + final FaceAuthenticationClient client = createClient(2); + client.start(mCallback); + client.onLockoutPermanent(); + + verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(), + eq(FACE_ERROR_LOCKOUT_PERMANENT), anyInt()); + verify(mCallback).onClientFinished(client, false); + } + + @Test + public void testTemporaryLockoutEndsOperation() throws RemoteException { + final FaceAuthenticationClient client = createClient(2); + client.start(mCallback); + client.onLockoutTimed(1000); + + verify(mClientMonitorCallbackConverter).onError(anyInt(), anyInt(), + eq(FACE_ERROR_LOCKOUT), anyInt()); + verify(mCallback).onClientFinished(client, false); + } + + @Test public void notifyHalWhenContextChanges() throws RemoteException { final FaceAuthenticationClient client = createClient(); client.start(mCallback); diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java index 120ddf6af7de..b539a76b521c 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java @@ -86,6 +86,7 @@ public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase { // Test setting default restrictions for managed profile. @Test + @Ignore("b/277916462") public void testMigration_managedProfileOwner() throws Exception { // Create a managed profile user. final File user10dir = getServices().addUser(10, 0, diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index dd81abeec5d3..16aadacb5af6 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -1368,6 +1368,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testClearDeviceOwner() throws Exception { mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); mContext.callerPermissions.add(permission.MANAGE_USERS); @@ -1955,6 +1956,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testSetUserRestriction_asDo() throws Exception { mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); mContext.callerPermissions.add(permission.MANAGE_USERS); @@ -2123,6 +2125,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testSetUserRestriction_asPo() { setAsProfileOwner(admin1); @@ -2259,6 +2262,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { ); @Test + @Ignore("b/277916462") public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception { final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(CALLER_USER_HANDLE, DpmMockContext.SYSTEM_UID); @@ -2334,6 +2338,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testNoDefaultEnabledUserRestrictions() throws Exception { mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS); mContext.callerPermissions.add(permission.MANAGE_USERS); @@ -2925,6 +2930,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testCreateAdminSupportIntent() throws Exception { // Setup device owner. mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID; @@ -4993,6 +4999,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testWipeDataManagedProfileOnOrganizationOwnedDevice() throws Exception { setupProfileOwner(); configureProfileOwnerOfOrgOwnedDevice(admin1, CALLER_USER_HANDLE); @@ -7023,6 +7030,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testSetUserControlDisabledPackages_asDO() throws Exception { final List<String> testPackages = new ArrayList<>(); testPackages.add("package_1"); @@ -7038,6 +7046,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testSetUserControlDisabledPackages_asPO() { final List<String> testPackages = new ArrayList<>(); testPackages.add("package_1"); @@ -7776,6 +7785,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testSetUserRestriction_financeDo_validRestrictions_setsRestriction() throws Exception { setDeviceOwner(); @@ -7858,6 +7868,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testSetUninstallBlocked_financeDo_success() throws Exception { String packageName = "com.android.foo.package"; setDeviceOwner(); @@ -7871,6 +7882,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testSetUserControlDisabledPackages_financeDo_success() throws Exception { List<String> packages = new ArrayList<>(); packages.add("com.android.foo.package"); @@ -7960,6 +7972,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testAddPersistentPreferredActivity_financeDo_success() throws Exception { IntentFilter filter = new IntentFilter(); ComponentName target = new ComponentName(admin2.getPackageName(), "test.class"); @@ -7975,6 +7988,7 @@ public class DevicePolicyManagerTest extends DpmTestBase { } @Test + @Ignore("b/277916462") public void testClearPackagePersistentPreferredActvities_financeDo_success() throws Exception { String packageName = admin2.getPackageName(); setDeviceOwner(); diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java index a0fb3deeb131..21a11bc0e49e 100644 --- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java @@ -763,7 +763,7 @@ public class PowerManagerServiceTest { ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class); verify(mDreamManagerInternalMock).registerDreamManagerStateListener( dreamManagerStateListener.capture()); - dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false); + dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false); when(mBatteryManagerInternalMock.getPlugType()) .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK); @@ -793,7 +793,7 @@ public class PowerManagerServiceTest { ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class); verify(mDreamManagerInternalMock).registerDreamManagerStateListener( dreamManagerStateListener.capture()); - dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(true); + dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(true); when(mBatteryManagerInternalMock.getPlugType()) .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK); @@ -823,7 +823,7 @@ public class PowerManagerServiceTest { ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class); verify(mDreamManagerInternalMock).registerDreamManagerStateListener( dreamManagerStateListener.capture()); - dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(true); + dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(true); when(mBatteryManagerInternalMock.getPlugType()) .thenReturn(BatteryManager.BATTERY_PLUGGED_DOCK); @@ -831,7 +831,7 @@ public class PowerManagerServiceTest { forceAwake(); // Needs to be awake first before it can dream. forceDream(); - dreamManagerStateListener.getValue().onKeepDreamingWhenUndockedChanged(false); + dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false); when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0); setPluggedIn(false); @@ -839,6 +839,34 @@ public class PowerManagerServiceTest { } @Test + public void testWakefulnessDream_shouldStopDreamingWhenUnplugging_whenDreamPrevents() { + // Make sure "unplug turns on screen" is configured to true. + when(mResourcesSpy.getBoolean(com.android.internal.R.bool.config_unplugTurnsOnScreen)) + .thenReturn(true); + + createService(); + startSystem(); + + ArgumentCaptor<DreamManagerInternal.DreamManagerStateListener> dreamManagerStateListener = + ArgumentCaptor.forClass(DreamManagerInternal.DreamManagerStateListener.class); + verify(mDreamManagerInternalMock).registerDreamManagerStateListener( + dreamManagerStateListener.capture()); + + when(mBatteryManagerInternalMock.getPlugType()) + .thenReturn(BatteryManager.BATTERY_PLUGGED_AC); + setPluggedIn(true); + + forceAwake(); // Needs to be awake first before it can dream. + forceDream(); + dreamManagerStateListener.getValue().onKeepDreamingWhenUnpluggingChanged(false); + when(mBatteryManagerInternalMock.getPlugType()).thenReturn(0); + setPluggedIn(false); + + assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE); + } + + + @Test public void testWakefulnessDoze_goToSleep() { createService(); // Start with AWAKE state diff --git a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java b/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java index 2230ddd8a4a7..2bde51b4eb57 100644 --- a/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java +++ b/services/tests/servicestests/src/com/android/server/power/ShutdownCheckPointsTest.java @@ -103,21 +103,45 @@ public class ShutdownCheckPointsTest { } @Test - public void testCallerProcessBinderEntry() throws RemoteException { + public void testCallerProcessBinderEntries() throws RemoteException { List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfos = new ArrayList<>(); runningAppProcessInfos.add( new ActivityManager.RunningAppProcessInfo("process_name", 1, new String[0])); when(mActivityManager.getRunningAppProcesses()).thenReturn(runningAppProcessInfos); mTestInjector.setCurrentTime(1000); + // Matching pid in getRunningAppProcesses mInstance.recordCheckPointInternal(1, "reason1"); + // Mising pid in getRunningAppProcesses + mInstance.recordCheckPointInternal(2, "reason2"); assertEquals( "Shutdown request from BINDER for reason reason1 " + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n" + "com.android.server.power.ShutdownCheckPointsTest" - + ".testCallerProcessBinderEntry\n" - + "From process process_name (pid=1)\n\n", + + ".testCallerProcessBinderEntries\n" + + "From process process_name (pid=1)\n\n" + + "Shutdown request from BINDER for reason reason2 " + + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n" + + "com.android.server.power.ShutdownCheckPointsTest" + + ".testCallerProcessBinderEntries\n" + + "From process ? (pid=2)\n\n", + dumpToString()); + } + + @Test + public void testNullCallerProcessBinderEntries() throws RemoteException { + when(mActivityManager.getRunningAppProcesses()).thenReturn(null); + + mTestInjector.setCurrentTime(1000); + mInstance.recordCheckPointInternal(1, "reason1"); + + assertEquals( + "Shutdown request from BINDER for reason reason1 " + + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n" + + "com.android.server.power.ShutdownCheckPointsTest" + + ".testNullCallerProcessBinderEntries\n" + + "From process ? (pid=1)\n\n", dumpToString()); } diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java index 7cf5bc88c213..dca67d6da7ad 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/CpuWakeupStatsTest.java @@ -14,13 +14,14 @@ * limitations under the License. */ -package com.android.server.power.stats; +package com.android.server.power.stats.wakeups; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_ALARM; +import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_UNKNOWN; import static android.os.BatteryStatsInternal.CPU_WAKEUP_SUBSYSTEM_WIFI; -import static com.android.server.power.stats.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS; +import static com.android.server.power.stats.wakeups.CpuWakeupStats.WAKEUP_REASON_HALF_WINDOW_MS; import static com.google.common.truth.Truth.assertThat; @@ -34,6 +35,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.android.frameworks.servicestests.R; +import com.android.server.power.stats.wakeups.CpuWakeupStats.Wakeup; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,10 +48,11 @@ import java.util.concurrent.ThreadLocalRandom; @RunWith(AndroidJUnit4.class) public class CpuWakeupStatsTest { private static final String KERNEL_REASON_ALARM_IRQ = "120 test.alarm.device"; - private static final String KERNEL_REASON_WIFI_IRQ = "120 test.wifi.device"; + private static final String KERNEL_REASON_WIFI_IRQ = "130 test.wifi.device"; + private static final String KERNEL_REASON_SOUND_TRIGGER_IRQ = "129 test.sound_trigger.device"; private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device"; private static final String KERNEL_REASON_UNKNOWN = "free-form-reason test.alarm.device"; - private static final String KERNEL_REASON_UNSUPPORTED = "-1 test.alarm.device"; + private static final String KERNEL_REASON_ALARM_ABNORMAL = "-1 test.alarm.device"; private static final String KERNEL_REASON_ABORT = "Abort: due to test.alarm.device"; private static final int TEST_UID_1 = 13239823; @@ -140,6 +143,40 @@ public class CpuWakeupStatsTest { } @Test + public void soundTriggerIrqAttributionSolo() { + final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler); + final long wakeupTime = 1247121; + + populateDefaultProcStates(obj); + + obj.noteWakeupTimeAndReason(wakeupTime, 1, KERNEL_REASON_SOUND_TRIGGER_IRQ); + + // Outside the window, so should be ignored. + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, + wakeupTime - WAKEUP_REASON_HALF_WINDOW_MS - 1, TEST_UID_1); + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, + wakeupTime + WAKEUP_REASON_HALF_WINDOW_MS + 1, TEST_UID_2); + // Should be attributed + obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER, wakeupTime + 5, TEST_UID_3, + TEST_UID_5); + + final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime); + assertThat(attribution).isNotNull(); + assertThat(attribution.size()).isEqualTo(1); + assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER)).isTrue(); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).indexOfKey( + TEST_UID_1)).isLessThan(0); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).indexOfKey( + TEST_UID_2)).isLessThan(0); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).get(TEST_UID_3)).isEqualTo( + TEST_PROC_STATE_3); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).indexOfKey( + TEST_UID_4)).isLessThan(0); + assertThat(attribution.get(CPU_WAKEUP_SUBSYSTEM_SOUND_TRIGGER).get(TEST_UID_5)).isEqualTo( + TEST_PROC_STATE_5); + } + + @Test public void wifiIrqAttributionSolo() { final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler); final long wakeupTime = 12423121; @@ -268,22 +305,39 @@ public class CpuWakeupStatsTest { } @Test - public void unsupportedWakeupIgnored() { + public void abnormalAlarmAttribution() { final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler); + populateDefaultProcStates(obj); long wakeupTime = 970934; - obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNSUPPORTED); + obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_ALARM_ABNORMAL); - // Should be ignored as this type of wakeup is unsupported. - assertThat(obj.mWakeupEvents.size()).isEqualTo(0); + assertThat(obj.mWakeupEvents.size()).isEqualTo(1); + assertThat(obj.mWakeupEvents.valueAt(0).mType).isEqualTo(Wakeup.TYPE_ABNORMAL); obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3); obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4); - // Any nearby activity should not end up in the attribution map. - assertThat(obj.mWakeupAttribution.size()).isEqualTo(0); + final SparseArray<SparseIntArray> attribution = obj.mWakeupAttribution.get(wakeupTime); + assertThat(attribution.size()).isEqualTo(1); + assertThat(attribution.contains(CPU_WAKEUP_SUBSYSTEM_ALARM)).isTrue(); + 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).indexOfKey(TEST_UID_5)).isLessThan( + 0); + } + + @Test + public void abortIgnored() { + final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3, mHandler); - wakeupTime = 883124; + long wakeupTime = 883124; obj.noteWakeupTimeAndReason(wakeupTime, 3, KERNEL_REASON_ABORT); // Should be ignored as this type of wakeup is unsupported. diff --git a/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java index 43d9e60c4a2b..47a8f49e7025 100644 --- a/services/tests/servicestests/src/com/android/server/power/stats/IrqDeviceMapTest.java +++ b/services/tests/servicestests/src/com/android/server/power/stats/wakeups/IrqDeviceMapTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.power.stats; +package com.android.server.power.stats.wakeups; import static com.google.common.truth.Truth.assertThat; diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandlerTest.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandlerTest.java new file mode 100644 index 000000000000..bc94dacea324 --- /dev/null +++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandlerTest.java @@ -0,0 +1,335 @@ +/* + * 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.server.soundtrigger_middleware; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import android.media.soundtrigger.ModelParameterRange; +import android.media.soundtrigger.Phrase; +import android.media.soundtrigger.PhraseSoundModel; +import android.media.soundtrigger.Properties; +import android.media.soundtrigger.RecognitionConfig; +import android.media.soundtrigger.RecognitionMode; +import android.media.soundtrigger.SoundModel; +import android.media.soundtrigger.SoundModelType; +import android.media.soundtrigger.Status; +import android.os.IBinder; + +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; +import org.mockito.InOrder; +import org.mockito.MockitoAnnotations; + +@RunWith(JUnit4.class) +public final class SoundTriggerDuplicateModelHandlerTest { + // Component under test + private SoundTriggerDuplicateModelHandler mComponent; + + private static final String DUPLICATE_UUID = "abcddead-beef-0123-3210-0123456789ab"; + private static final String DIFFERENT_UUID = "0000dead-beef-0123-3210-0123456789ab"; + + @Mock private ISoundTriggerHal mUnderlying; + @Mock private ISoundTriggerHal.GlobalCallback mGlobalCallback; + @Mock private ISoundTriggerHal.ModelCallback mModelCallback; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mComponent = new SoundTriggerDuplicateModelHandler(mUnderlying); + doNothing().when(mUnderlying).registerCallback(any()); + mComponent.registerCallback(mGlobalCallback); + verify(mUnderlying).registerCallback(eq(mGlobalCallback)); + } + + @Test + public void loadSoundModel_throwsResourceContention_whenDuplicateUuid() { + final var soundModel = createSoundModelOne(); + final var soundModelSameUuid = createSoundModelTwo(); + // First sound model load should complete successfully + mComponent.loadSoundModel(soundModel, mModelCallback); + verify(mUnderlying).loadSoundModel(eq(soundModel), eq(mModelCallback)); + assertEquals( + assertThrows( + RecoverableException.class, + () -> mComponent.loadSoundModel(soundModelSameUuid, mModelCallback)) + .errorCode, + Status.RESOURCE_CONTENTION); + // Model has not been unloaded, so we don't get a callback + verify(mGlobalCallback, never()).onResourcesAvailable(); + verifyNoMoreInteractions(mUnderlying); + verifyNoMoreInteractions(mGlobalCallback); + } + + @Test + public void loadSoundModel_doesNotThrowResourceContention_whenDifferentUuid() { + final var soundModel = createSoundModelOne(); + // Make all other fields the same + final var soundModelDifferentUuid = createSoundModelOne(); + soundModelDifferentUuid.uuid = DIFFERENT_UUID; + InOrder inOrder = Mockito.inOrder(mUnderlying); + // First sound model load should complete successfully + mComponent.loadSoundModel(soundModel, mModelCallback); + inOrder.verify(mUnderlying).loadSoundModel(eq(soundModel), eq(mModelCallback)); + mComponent.loadSoundModel(soundModelDifferentUuid, mModelCallback); + inOrder.verify(mUnderlying).loadSoundModel(eq(soundModelDifferentUuid), eq(mModelCallback)); + // No contention, so we don't get a callback + verify(mGlobalCallback, never()).onResourcesAvailable(); + verifyNoMoreInteractions(mUnderlying); + verifyNoMoreInteractions(mGlobalCallback); + } + + @Test + public void loadSoundModel_doesNotThrow_afterDuplicateUuidHasBeenUnloaded() { + final var soundModel = createSoundModelOne(); + // First sound model load should complete successfully + int handle = mComponent.loadSoundModel(soundModel, mModelCallback); + verify(mUnderlying).loadSoundModel(eq(soundModel), eq(mModelCallback)); + // Unload model should complete successfully + mComponent.unloadSoundModel(handle); + verify(mUnderlying).unloadSoundModel(eq(handle)); + // Since the model with the same UUID was unloaded, the subsequent load model + // should succeed. + mComponent.loadSoundModel(soundModel, mModelCallback); + verify(mUnderlying, times(2)).loadSoundModel(eq(soundModel), eq(mModelCallback)); + verifyNoMoreInteractions(mUnderlying); + verifyNoMoreInteractions(mGlobalCallback); + } + + @Test + public void unloadSoundModel_triggersResourceCallback_afterDuplicateUuidRejected() { + final var soundModel = createSoundModelOne(); + final var soundModelSameUuid = createSoundModelTwo(); + // First sound model load should complete successfully + int handle = mComponent.loadSoundModel(soundModel, mModelCallback); + verify(mUnderlying).loadSoundModel(eq(soundModel), eq(mModelCallback)); + assertEquals( + assertThrows( + RecoverableException.class, + () -> mComponent.loadSoundModel(soundModelSameUuid, mModelCallback)) + .errorCode, + Status.RESOURCE_CONTENTION); + mComponent.unloadSoundModel(handle); + verify(mUnderlying).unloadSoundModel(eq(handle)); + verify(mGlobalCallback).onResourcesAvailable(); + verifyNoMoreInteractions(mUnderlying); + verifyNoMoreInteractions(mGlobalCallback); + } + + // Next tests are same as above, but for phrase sound model. + @Test + public void loadPhraseSoundModel_throwsResourceContention_whenDuplicateUuid() { + final var soundModel = createPhraseSoundModelOne(); + final var soundModelSameUuid = createPhraseSoundModelTwo(); + // First sound model load should complete successfully + mComponent.loadPhraseSoundModel(soundModel, mModelCallback); + verify(mUnderlying).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback)); + assertEquals( + assertThrows( + RecoverableException.class, + () -> + mComponent.loadPhraseSoundModel( + soundModelSameUuid, mModelCallback)) + .errorCode, + Status.RESOURCE_CONTENTION); + // Model has not been unloaded, so we don't get a callback + verify(mGlobalCallback, never()).onResourcesAvailable(); + verifyNoMoreInteractions(mUnderlying); + verifyNoMoreInteractions(mGlobalCallback); + } + + @Test + public void loadPhraseSoundModel_doesNotThrowResourceContention_whenDifferentUuid() { + final var soundModel = createPhraseSoundModelOne(); + // Make all other fields the same + final var soundModelDifferentUuid = createPhraseSoundModelOne(); + soundModelDifferentUuid.common.uuid = DIFFERENT_UUID; + InOrder inOrder = Mockito.inOrder(mUnderlying); + // First sound model load should complete successfully + mComponent.loadPhraseSoundModel(soundModel, mModelCallback); + inOrder.verify(mUnderlying).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback)); + mComponent.loadPhraseSoundModel(soundModelDifferentUuid, mModelCallback); + inOrder.verify(mUnderlying).loadPhraseSoundModel(eq(soundModelDifferentUuid), + eq(mModelCallback)); + // No contention, so we don't get a callback + verify(mGlobalCallback, never()).onResourcesAvailable(); + verifyNoMoreInteractions(mUnderlying); + verifyNoMoreInteractions(mGlobalCallback); + } + + @Test + public void loadPhraseSoundModel_doesNotThrow_afterDuplicateUuidHasBeenUnloaded() { + final var soundModel = createPhraseSoundModelOne(); + // First sound model load should complete successfully + int handle = mComponent.loadPhraseSoundModel(soundModel, mModelCallback); + verify(mUnderlying).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback)); + // Unload model should complete successfully + mComponent.unloadSoundModel(handle); + verify(mUnderlying).unloadSoundModel(eq(handle)); + // Since the model with the same UUID was unloaded, the subsequent load model + // should succeed. + mComponent.loadPhraseSoundModel(soundModel, mModelCallback); + verify(mUnderlying, times(2)).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback)); + verifyNoMoreInteractions(mUnderlying); + verifyNoMoreInteractions(mGlobalCallback); + } + + @Test + public void unloadSoundModel_triggersResourceCallback_afterDuplicateUuidRejectedPhrase() { + final var soundModel = createPhraseSoundModelOne(); + final var soundModelSameUuid = createPhraseSoundModelTwo(); + // First sound model load should complete successfully + int handle = mComponent.loadPhraseSoundModel(soundModel, mModelCallback); + verify(mUnderlying).loadPhraseSoundModel(eq(soundModel), eq(mModelCallback)); + assertEquals( + assertThrows( + RecoverableException.class, + () -> + mComponent.loadPhraseSoundModel( + soundModelSameUuid, mModelCallback)) + .errorCode, + Status.RESOURCE_CONTENTION); + mComponent.unloadSoundModel(handle); + verify(mUnderlying).unloadSoundModel(eq(handle)); + verify(mGlobalCallback).onResourcesAvailable(); + verifyNoMoreInteractions(mUnderlying); + verifyNoMoreInteractions(mGlobalCallback); + } + + @Test + public void testDelegation() { + // Test that the rest of the interface delegates its calls to the underlying object + // appropriately. + // This test method does not test load/unloadSoundModel + var properties = new Properties(); + InOrder inOrder = Mockito.inOrder(mUnderlying); + doReturn(properties).when(mUnderlying).getProperties(); + assertEquals(mComponent.getProperties(), properties); + inOrder.verify(mUnderlying).getProperties(); + var mockGlobalCallback = mock(ISoundTriggerHal.GlobalCallback.class); + mComponent.registerCallback(mockGlobalCallback); + inOrder.verify(mUnderlying).registerCallback(eq(mockGlobalCallback)); + int modelId = 5; + int deviceHandle = 2; + int ioHandle = 3; + var config = mock(RecognitionConfig.class); + mComponent.startRecognition(modelId, deviceHandle, ioHandle, config); + inOrder.verify(mUnderlying) + .startRecognition(eq(modelId), eq(deviceHandle), eq(ioHandle), eq(config)); + + mComponent.stopRecognition(modelId); + inOrder.verify(mUnderlying).stopRecognition(eq(modelId)); + mComponent.forceRecognitionEvent(modelId); + inOrder.verify(mUnderlying).forceRecognitionEvent(eq(modelId)); + int param = 10; + int value = 50; + var modelParamRange = new ModelParameterRange(); + doReturn(modelParamRange).when(mUnderlying).queryParameter(anyInt(), anyInt()); + assertEquals(mComponent.queryParameter(param, value), modelParamRange); + inOrder.verify(mUnderlying).queryParameter(param, value); + doReturn(value).when(mUnderlying).getModelParameter(anyInt(), anyInt()); + assertEquals(mComponent.getModelParameter(modelId, param), value); + inOrder.verify(mUnderlying).getModelParameter(eq(modelId), eq(param)); + mComponent.setModelParameter(modelId, param, value); + inOrder.verify(mUnderlying).setModelParameter(eq(modelId), eq(param), eq(value)); + var recipient = mock(IBinder.DeathRecipient.class); + mComponent.linkToDeath(recipient); + inOrder.verify(mUnderlying).linkToDeath(eq(recipient)); + mComponent.unlinkToDeath(recipient); + inOrder.verify(mUnderlying).unlinkToDeath(eq(recipient)); + mComponent.flushCallbacks(); + inOrder.verify(mUnderlying).flushCallbacks(); + var token = mock(IBinder.class); + mComponent.clientAttached(token); + inOrder.verify(mUnderlying).clientAttached(eq(token)); + mComponent.clientDetached(token); + inOrder.verify(mUnderlying).clientDetached(eq(token)); + mComponent.reboot(); + inOrder.verify(mUnderlying).reboot(); + mComponent.detach(); + inOrder.verify(mUnderlying).detach(); + verifyNoMoreInteractions(mUnderlying); + verifyNoMoreInteractions(mGlobalCallback); + } + + private static SoundModel createSoundModelOne() { + SoundModel model = new SoundModel(); + model.type = SoundModelType.GENERIC; + model.uuid = DUPLICATE_UUID; + model.vendorUuid = "87654321-5432-6543-7654-456789fedcba"; + byte[] data = new byte[] {91, 92, 93, 94, 95}; + model.data = TestUtil.byteArrayToParcelFileDescriptor(data); + model.dataSize = data.length; + return model; + } + + // Different except for the same UUID + private static SoundModel createSoundModelTwo() { + SoundModel model = new SoundModel(); + model.type = SoundModelType.GENERIC; + model.uuid = DUPLICATE_UUID; + model.vendorUuid = "12345678-9876-5432-1012-345678901234"; + byte[] data = new byte[] {19, 18, 17, 16}; + model.data = TestUtil.byteArrayToParcelFileDescriptor(data); + model.dataSize = data.length; + return model; + } + + private static PhraseSoundModel createPhraseSoundModelOne() { + PhraseSoundModel model = new PhraseSoundModel(); + model.common = createSoundModelOne(); + model.common.type = SoundModelType.KEYPHRASE; + model.phrases = new Phrase[1]; + model.phrases[0] = new Phrase(); + model.phrases[0].id = 123; + model.phrases[0].users = new int[] {5, 6, 7}; + model.phrases[0].locale = "locale"; + model.phrases[0].text = "text"; + model.phrases[0].recognitionModes = + RecognitionMode.USER_AUTHENTICATION | RecognitionMode.USER_IDENTIFICATION; + return model; + } + + private static PhraseSoundModel createPhraseSoundModelTwo() { + PhraseSoundModel model = new PhraseSoundModel(); + model.common = createSoundModelTwo(); + model.common.type = SoundModelType.KEYPHRASE; + model.phrases = new Phrase[1]; + model.phrases[0] = new Phrase(); + model.phrases[0].id = 321; + model.phrases[0].users = new int[] {4, 3, 2, 1}; + model.phrases[0].locale = "differentLocale"; + model.phrases[0].text = "differentText"; + model.phrases[0].recognitionModes = 0; + return model; + } +} diff --git a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java index 39561f74d7ed..3b7bc952639e 100644 --- a/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java +++ b/services/tests/voiceinteractiontests/src/com/android/server/soundtrigger_middleware/TestUtil.java @@ -466,7 +466,7 @@ class TestUtil { assertEquals(43, event.phraseExtras[0].levels[0].levelPercent); } - private static ParcelFileDescriptor byteArrayToParcelFileDescriptor(byte[] data) { + static ParcelFileDescriptor byteArrayToParcelFileDescriptor(byte[] data) { try (SharedMemory shmem = SharedMemory.create("", data.length)) { ByteBuffer buffer = shmem.mapReadWrite(); buffer.put(data); diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 2696d2bf58b9..f12b53acd06b 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -87,6 +87,8 @@ android:showWhenLocked="true"/> <activity android:name="android.view.cts.surfacevalidator.CapturedActivity"/> + <activity android:name="com.android.server.wm.SurfaceControlViewHostTests$TestActivity" /> + <service android:name="android.view.cts.surfacevalidator.LocalMediaProjectionService" android:foregroundServiceType="mediaProjection" android:enabled="true"> diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java new file mode 100644 index 000000000000..41bfc806f839 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceControlViewHostTests.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.Manifest.permission.ACCESS_SURFACE_FLINGER; +import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW; +import static android.server.wm.CtsWindowInfoUtils.waitForWindowVisible; +import static android.server.wm.CtsWindowInfoUtils.waitForWindowFocus; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; + +import static org.junit.Assert.assertTrue; + +import android.app.Activity; +import android.app.Instrumentation; +import android.content.res.Configuration; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; +import android.view.Gravity; +import android.view.IWindow; +import android.view.SurfaceControl; +import android.view.SurfaceControlViewHost; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; +import android.view.WindowManagerGlobal; +import android.view.WindowlessWindowManager; +import android.widget.Button; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.rule.ActivityTestRule; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; + +@Presubmit +@SmallTest +@RunWith(WindowTestRunner.class) +public class SurfaceControlViewHostTests { + private final ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule<>( + TestActivity.class); + private Instrumentation mInstrumentation; + private TestActivity mActivity; + + private View mView1; + private View mView2; + private SurfaceControlViewHost mScvh1; + private SurfaceControlViewHost mScvh2; + + @Before + public void setUp() throws Exception { + mInstrumentation = InstrumentationRegistry.getInstrumentation(); + + // ACCESS_SURFACE_FLINGER is necessary to call waitForWindow + // INTERNAL_SYSTEM_WINDOW is necessary to add SCVH with no host parent + mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(ACCESS_SURFACE_FLINGER, + INTERNAL_SYSTEM_WINDOW); + mActivity = mActivityRule.launchActivity(null); + } + + @After + public void tearDown() { + mInstrumentation.getUiAutomation().dropShellPermissionIdentity(); + } + + @Test + public void requestFocusWithMultipleWindows() throws InterruptedException, RemoteException { + SurfaceControl sc = new SurfaceControl.Builder() + .setName("SurfaceControlViewHostTests") + .setCallsite("requestFocusWithMultipleWindows") + .build(); + mView1 = new Button(mActivity); + mView2 = new Button(mActivity); + + mActivity.runOnUiThread(() -> { + TestWindowlessWindowManager wwm = new TestWindowlessWindowManager( + mActivity.getResources().getConfiguration(), sc, null); + + try { + mActivity.attachToSurfaceView(sc); + } catch (InterruptedException e) { + } + + mScvh1 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), + wwm, "requestFocusWithMultipleWindows"); + mScvh2 = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(), + wwm, "requestFocusWithMultipleWindows"); + + + mView1.setBackgroundColor(Color.RED); + mView2.setBackgroundColor(Color.BLUE); + + WindowManager.LayoutParams lp1 = new WindowManager.LayoutParams(200, 200, + TYPE_APPLICATION, 0, PixelFormat.OPAQUE); + WindowManager.LayoutParams lp2 = new WindowManager.LayoutParams(100, 100, + TYPE_APPLICATION, 0, PixelFormat.OPAQUE); + mScvh1.setView(mView1, lp1); + mScvh2.setView(mView2, lp2); + }); + + assertTrue("Failed to wait for view1", waitForWindowVisible(mView1)); + assertTrue("Failed to wait for view2", waitForWindowVisible(mView2)); + + WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */, + mScvh1.getFocusGrantToken(), true); + assertTrue("Failed to gain focus for view1", waitForWindowFocus(mView1, true)); + + WindowManagerGlobal.getWindowSession().grantEmbeddedWindowFocus(null /* window */, + mScvh2.getFocusGrantToken(), true); + assertTrue("Failed to gain focus for view2", waitForWindowFocus(mView2, true)); + } + + private static class TestWindowlessWindowManager extends WindowlessWindowManager { + private final SurfaceControl mRoot; + + TestWindowlessWindowManager(Configuration c, SurfaceControl rootSurface, + IBinder hostInputToken) { + super(c, rootSurface, hostInputToken); + mRoot = rootSurface; + } + + @Override + protected SurfaceControl getParentSurface(IWindow window, + WindowManager.LayoutParams attrs) { + return mRoot; + } + } + + public static class TestActivity extends Activity implements SurfaceHolder.Callback { + private SurfaceView mSurfaceView; + private final CountDownLatch mSvReadyLatch = new CountDownLatch(1); + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final FrameLayout content = new FrameLayout(this); + mSurfaceView = new SurfaceView(this); + mSurfaceView.setBackgroundColor(Color.BLACK); + mSurfaceView.setZOrderOnTop(true); + final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(500, 500, + Gravity.LEFT | Gravity.TOP); + content.addView(mSurfaceView, lp); + setContentView(content); + mSurfaceView.getHolder().addCallback(this); + } + + @Override + public void surfaceCreated(@NonNull SurfaceHolder holder) { + mSvReadyLatch.countDown(); + } + + @Override + public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, + int height) { + } + + @Override + public void surfaceDestroyed(@NonNull SurfaceHolder holder) { + } + + public void attachToSurfaceView(SurfaceControl sc) throws InterruptedException { + mSvReadyLatch.await(); + new SurfaceControl.Transaction().reparent(sc, mSurfaceView.getSurfaceControl()) + .show(sc).apply(); + } + } +} + 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 1e2fdec07d00..43b429c76749 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java @@ -1410,9 +1410,9 @@ public class TransitionTests extends WindowTestsBase { final Transition.ChangeInfo task1ChangeInfo = closeTransition.mChanges.get(task1); assertNotNull(task1ChangeInfo); assertTrue(task1ChangeInfo.hasChanged()); + // Make sure the unrelated activity is NOT collected. final Transition.ChangeInfo activity1ChangeInfo = closeTransition.mChanges.get(activity1); - assertNotNull(activity1ChangeInfo); - assertTrue(activity1ChangeInfo.hasChanged()); + assertNull(activity1ChangeInfo); // No need to wait for the activity in transient hide task. assertEquals(WindowContainer.SYNC_STATE_NONE, activity1.mSyncState); @@ -1442,6 +1442,7 @@ public class TransitionTests extends WindowTestsBase { } } }); + assertTrue(activity1.isVisible()); controller.finishTransition(closeTransition); assertTrue(wasInFinishingTransition[0]); assertNull(controller.mFinishingTransition); @@ -1450,6 +1451,7 @@ public class TransitionTests extends WindowTestsBase { assertEquals(ActivityTaskManagerService.APP_SWITCH_DISALLOW, mAtm.getBalAppSwitchesState()); // Because task1 is occluded by task2, finishTransition should make activity1 invisible. assertFalse(activity1.isVisibleRequested()); + // Make sure activity1 visibility was committed assertFalse(activity1.isVisible()); assertFalse(activity1.app.hasActivityInVisibleTask()); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 5efd158133ed..07dc1c66bc4d 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -315,12 +315,13 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig, int keyphraseId, boolean runInBatterySaverMode) { synchronized (mLock) { + // TODO Remove previous callback handling IRecognitionStatusCallback oldCallback = modelData.getCallback(); if (oldCallback != null && oldCallback.asBinder() != callback.asBinder()) { Slog.w(TAG, "Canceling previous recognition for model id: " + modelData.getModelId()); try { - oldCallback.onError(STATUS_ERROR); + oldCallback.onPreempted(); } catch (RemoteException e) { Slog.w(TAG, "RemoteException in onDetectionStopped", e); } @@ -759,15 +760,12 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { onRecognitionAbortLocked(event); break; case SoundTrigger.RECOGNITION_STATUS_FAILURE: - // Fire failures to all listeners since it's not tied to a keyphrase. - onRecognitionFailureLocked(); - break; case SoundTrigger.RECOGNITION_STATUS_SUCCESS: case SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE: if (isKeyphraseRecognitionEvent(event)) { - onKeyphraseRecognitionSuccessLocked((KeyphraseRecognitionEvent) event); + onKeyphraseRecognitionLocked((KeyphraseRecognitionEvent) event); } else { - onGenericRecognitionSuccessLocked((GenericRecognitionEvent) event); + onGenericRecognitionLocked((GenericRecognitionEvent) event); } break; } @@ -778,7 +776,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return event instanceof KeyphraseRecognitionEvent; } - private void onGenericRecognitionSuccessLocked(GenericRecognitionEvent event) { + private void onGenericRecognitionLocked(GenericRecognitionEvent event) { MetricsLogger.count(mContext, "sth_generic_recognition_event", 1); if (event.status != SoundTrigger.RECOGNITION_STATUS_SUCCESS && event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) { @@ -901,17 +899,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } - private void onRecognitionFailureLocked() { - Slog.w(TAG, "Recognition failure"); - MetricsLogger.count(mContext, "sth_recognition_failure_event", 1); - try { - sendErrorCallbacksToAllLocked(STATUS_ERROR); - } finally { - internalClearModelStateLocked(); - internalClearGlobalStateLocked(); - } - } - private int getKeyphraseIdFromEvent(KeyphraseRecognitionEvent event) { if (event == null) { Slog.w(TAG, "Null RecognitionEvent received."); @@ -927,7 +914,7 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { return keyphraseExtras[0].id; } - private void onKeyphraseRecognitionSuccessLocked(KeyphraseRecognitionEvent event) { + private void onKeyphraseRecognitionLocked(KeyphraseRecognitionEvent event) { Slog.i(TAG, "Recognition success"); MetricsLogger.count(mContext, "sth_keyphrase_recognition_event", 1); int keyphraseId = getKeyphraseIdFromEvent(event); @@ -1001,7 +988,17 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { private void onServiceDiedLocked() { try { MetricsLogger.count(mContext, "sth_service_died", 1); - sendErrorCallbacksToAllLocked(SoundTrigger.STATUS_DEAD_OBJECT); + for (ModelData modelData : mModelDataMap.values()) { + IRecognitionStatusCallback callback = modelData.getCallback(); + if (callback != null) { + try { + callback.onModuleDied(); + } catch (RemoteException e) { + Slog.w(TAG, "RemoteException send moduleDied for model handle " + + modelData.getHandle(), e); + } + } + } } finally { internalClearModelStateLocked(); internalClearGlobalStateLocked(); @@ -1111,21 +1108,6 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { } } - // Sends an error callback to all models with a valid registered callback. - private void sendErrorCallbacksToAllLocked(int errorCode) { - for (ModelData modelData : mModelDataMap.values()) { - IRecognitionStatusCallback callback = modelData.getCallback(); - if (callback != null) { - try { - callback.onError(errorCode); - } catch (RemoteException e) { - Slog.w(TAG, "RemoteException sendErrorCallbacksToAllLocked for model handle " + - modelData.getHandle(), e); - } - } - } - } - /** * Stops and unloads all models. This is intended as a clean-up call with the expectation that * this instance is not used after. @@ -1342,11 +1324,11 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { // Notify of error if needed. if (notifyClientOnError) { try { - callback.onError(status); + callback.onResumeFailed(status); } catch (DeadObjectException e) { forceStopAndUnloadModelLocked(modelData, e); } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in onError", e); + Slog.w(TAG, "RemoteException in onResumeFailed", e); } } } else { @@ -1382,15 +1364,15 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { status = mModule.stopRecognition(modelData.getHandle()); if (status != SoundTrigger.STATUS_OK) { - Slog.w(TAG, "stopRecognition call failed with " + status); + Slog.e(TAG, "stopRecognition call failed with " + status); MetricsLogger.count(mContext, "sth_stop_recognition_error", 1); if (notify) { try { - callback.onError(status); + callback.onPauseFailed(status); } catch (DeadObjectException e) { forceStopAndUnloadModelLocked(modelData, e); } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in onError", e); + Slog.w(TAG, "RemoteException in onPauseFailed", e); } } } else { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 203a3e74d9da..1bbea89f5acb 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -25,6 +25,7 @@ import static android.content.pm.PackageManager.GET_META_DATA; import static android.content.pm.PackageManager.GET_SERVICES; import static android.content.pm.PackageManager.MATCH_DEBUG_TRIAGED_MISSING; import static android.hardware.soundtrigger.SoundTrigger.STATUS_BAD_VALUE; +import static android.hardware.soundtrigger.SoundTrigger.STATUS_DEAD_OBJECT; import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR; import static android.hardware.soundtrigger.SoundTrigger.STATUS_OK; import static android.provider.Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY; @@ -1388,8 +1389,7 @@ public class SoundTriggerService extends SystemService { })); } - @Override - public void onError(int status) { + private void onError(int status) { if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status); sEventLogger.enqueue(new EventLogger.StringEvent(mPuuid @@ -1412,6 +1412,30 @@ public class SoundTriggerService extends SystemService { } @Override + public void onPreempted() { + if (DEBUG) Slog.v(TAG, mPuuid + ": onPreempted"); + onError(STATUS_ERROR); + } + + @Override + public void onModuleDied() { + if (DEBUG) Slog.v(TAG, mPuuid + ": onModuleDied"); + onError(STATUS_DEAD_OBJECT); + } + + @Override + public void onResumeFailed(int status) { + if (DEBUG) Slog.v(TAG, mPuuid + ": onResumeFailed: " + status); + onError(status); + } + + @Override + public void onPauseFailed(int status) { + if (DEBUG) Slog.v(TAG, mPuuid + ": onPauseFailed: " + status); + onError(status); + } + + @Override public void onRecognitionPaused() { Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused"); diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandler.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandler.java new file mode 100644 index 000000000000..01041932567c --- /dev/null +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerDuplicateModelHandler.java @@ -0,0 +1,205 @@ +/* + * 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_middleware; + +import android.annotation.NonNull; +import android.media.soundtrigger.ModelParameterRange; +import android.media.soundtrigger.PhraseSoundModel; +import android.media.soundtrigger.Properties; +import android.media.soundtrigger.RecognitionConfig; +import android.media.soundtrigger.SoundModel; +import android.media.soundtrigger.Status; +import android.os.IBinder; + +import java.util.ArrayList; +import java.util.List; + +/** + * This wrapper prevents a models with the same UUID from being loaded concurrently. This is used to + * protect STHAL implementations, which don't support concurrent loads of the same model. We reject + * the duplicate load with {@link Status#RESOURCE_CONTENTION}. + */ +public class SoundTriggerDuplicateModelHandler implements ISoundTriggerHal { + private final @NonNull ISoundTriggerHal mDelegate; + + private GlobalCallback mGlobalCallback; + // There are rarely more than two models loaded. + private final List<ModelData> mModelList = new ArrayList<>(); + + private static final class ModelData { + ModelData(int modelId, String uuid) { + mModelId = modelId; + mUuid = uuid; + } + + int getModelId() { + return mModelId; + } + + String getUuid() { + return mUuid; + } + + boolean getWasContended() { + return mWasContended; + } + + void setWasContended() { + mWasContended = true; + } + + private int mModelId; + private String mUuid; + private boolean mWasContended = false; + } + + public SoundTriggerDuplicateModelHandler(@NonNull ISoundTriggerHal delegate) { + mDelegate = delegate; + } + + @Override + public void reboot() { + mDelegate.reboot(); + } + + @Override + public void detach() { + mDelegate.detach(); + } + + @Override + public Properties getProperties() { + return mDelegate.getProperties(); + } + + @Override + public void registerCallback(GlobalCallback callback) { + mGlobalCallback = callback; + mDelegate.registerCallback(mGlobalCallback); + } + + @Override + public int loadSoundModel(SoundModel soundModel, ModelCallback callback) { + synchronized (this) { + checkDuplicateModelUuid(soundModel.uuid); + var result = mDelegate.loadSoundModel(soundModel, callback); + mModelList.add(new ModelData(result, soundModel.uuid)); + return result; + } + } + + @Override + public int loadPhraseSoundModel(PhraseSoundModel soundModel, ModelCallback callback) { + synchronized (this) { + checkDuplicateModelUuid(soundModel.common.uuid); + var result = mDelegate.loadPhraseSoundModel(soundModel, callback); + mModelList.add(new ModelData(result, soundModel.common.uuid)); + return result; + } + } + + @Override + public void unloadSoundModel(int modelHandle) { + mDelegate.unloadSoundModel(modelHandle); + for (int i = 0; i < mModelList.size(); i++) { + if (mModelList.get(i).getModelId() == modelHandle) { + var modelData = mModelList.remove(i); + if (modelData.getWasContended()) { + mGlobalCallback.onResourcesAvailable(); + } + // Model ID is unique + return; + } + } + } + + // Uninteresting delegation calls to follow. + @Override + public void stopRecognition(int modelHandle) { + mDelegate.stopRecognition(modelHandle); + } + + @Override + public void startRecognition( + int modelHandle, int deviceHandle, int ioHandle, RecognitionConfig config) { + mDelegate.startRecognition(modelHandle, deviceHandle, ioHandle, config); + } + + @Override + public void forceRecognitionEvent(int modelHandle) { + mDelegate.forceRecognitionEvent(modelHandle); + } + + @Override + public int getModelParameter(int modelHandle, int param) { + return mDelegate.getModelParameter(modelHandle, param); + } + + @Override + public void setModelParameter(int modelHandle, int param, int value) { + mDelegate.setModelParameter(modelHandle, param, value); + } + + @Override + public ModelParameterRange queryParameter(int modelHandle, int param) { + return mDelegate.queryParameter(modelHandle, param); + } + + @Override + public void linkToDeath(IBinder.DeathRecipient recipient) { + mDelegate.linkToDeath(recipient); + } + + @Override + public void unlinkToDeath(IBinder.DeathRecipient recipient) { + mDelegate.unlinkToDeath(recipient); + } + + @Override + public String interfaceDescriptor() { + return mDelegate.interfaceDescriptor(); + } + + @Override + public void flushCallbacks() { + mDelegate.flushCallbacks(); + } + + @Override + public void clientAttached(IBinder binder) { + mDelegate.clientAttached(binder); + } + + @Override + public void clientDetached(IBinder binder) { + mDelegate.clientDetached(binder); + } + + /** + * Helper for handling duplicate model. If there is a load attempt for a model with a UUID which + * is already loaded: 1) Reject with {@link Status.RESOURCE_CONTENTION} 2) Mark the already + * loaded model as contended, as we need to dispatch a resource available callback following the + * original model being unloaded. + */ + private void checkDuplicateModelUuid(String uuid) { + var model = mModelList.stream().filter(x -> x.getUuid().equals(uuid)).findFirst(); + if (model.isPresent()) { + model.get().setWasContended(); + throw new RecoverableException(Status.RESOURCE_CONTENTION); + } + } +} diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java index d2d8f1ad7a71..6223b2e8ace4 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java @@ -170,7 +170,8 @@ class SoundTriggerModule implements IBinder.DeathRecipient, ISoundTriggerHal.Glo */ private void attachToHal() { mHalService = new SoundTriggerHalEnforcer( - new SoundTriggerHalWatchdog(mHalFactory.create())); + new SoundTriggerHalWatchdog( + new SoundTriggerDuplicateModelHandler(mHalFactory.create()))); mHalService.linkToDeath(this); mHalService.registerCallback(this); mProperties = mHalService.getProperties(); diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index f3cb9baedd4b..486945d9159e 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -56,6 +56,7 @@ import android.service.voice.HotwordDetector; import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback; import android.service.voice.ISandboxedDetectionService; import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback; +import android.service.voice.SoundTriggerFailure; import android.service.voice.VisualQueryDetectionService; import android.service.voice.VisualQueryDetectionServiceFailure; import android.service.voice.VoiceInteractionManagerInternal.HotwordDetectionServiceIdentity; @@ -153,7 +154,8 @@ final class HotwordDetectionConnection { private int mRestartCount = 0; @NonNull private ServiceConnection mRemoteHotwordDetectionService; @NonNull private ServiceConnection mRemoteVisualQueryDetectionService; - private IBinder mAudioFlinger; + @GuardedBy("mLock") + @Nullable private IBinder mAudioFlinger; @GuardedBy("mLock") private boolean mDebugHotwordLogging = false; @@ -193,7 +195,7 @@ final class HotwordDetectionConnection { new Intent(VisualQueryDetectionService.SERVICE_INTERFACE); visualQueryDetectionServiceIntent.setComponent(mVisualQueryDetectionComponentName); - initAudioFlingerLocked(); + initAudioFlinger(); mHotwordDetectionServiceConnectionFactory = new ServiceConnectionFactory(hotwordDetectionServiceIntent, @@ -226,31 +228,41 @@ final class HotwordDetectionConnection { } } - private void initAudioFlingerLocked() { + private void initAudioFlinger() { if (DEBUG) { - Slog.d(TAG, "initAudioFlingerLocked"); + Slog.d(TAG, "initAudioFlinger"); } - mAudioFlinger = ServiceManager.waitForService("media.audio_flinger"); - if (mAudioFlinger == null) { + final IBinder audioFlinger = ServiceManager.waitForService("media.audio_flinger"); + if (audioFlinger == null) { + setAudioFlinger(null); throw new IllegalStateException("Service media.audio_flinger wasn't found."); } if (DEBUG) { Slog.d(TAG, "Obtained audio_flinger binder."); } try { - mAudioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0); + audioFlinger.linkToDeath(mAudioServerDeathRecipient, /* flags= */ 0); } catch (RemoteException e) { Slog.w(TAG, "Audio server died before we registered a DeathRecipient; " - + "retrying init.", e); - initAudioFlingerLocked(); + + "retrying init.", e); + initAudioFlinger(); + return; + } + + setAudioFlinger(audioFlinger); + } + + private void setAudioFlinger(@Nullable IBinder audioFlinger) { + synchronized (mLock) { + mAudioFlinger = audioFlinger; } } private void audioServerDied() { Slog.w(TAG, "Audio server died; restarting the HotwordDetectionService."); + // TODO: Check if this needs to be scheduled on a different thread. + initAudioFlinger(); synchronized (mLock) { - // TODO: Check if this needs to be scheduled on a different thread. - initAudioFlingerLocked(); // We restart the process instead of simply sending over the new binder, to avoid race // conditions with audio reading in the service. restartProcessLocked(); @@ -576,8 +588,31 @@ final class HotwordDetectionConnection { } @Override - public void onError(int status) throws RemoteException { - mExternalCallback.onError(status); + public void onPreempted() throws RemoteException { + mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure( + SoundTriggerFailure.ERROR_CODE_UNEXPECTED_PREEMPTION, + "Unexpected startRecognition on already started ST session")); + } + + @Override + public void onModuleDied() throws RemoteException { + mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure( + SoundTriggerFailure.ERROR_CODE_MODULE_DIED, + "STHAL died")); + } + + @Override + public void onResumeFailed(int status) throws RemoteException { + mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure( + SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED, + "STService recognition resume failed with: " + status)); + } + + @Override + public void onPauseFailed(int status) throws RemoteException { + mExternalCallback.onSoundTriggerFailure(new SoundTriggerFailure( + SoundTriggerFailure.ERROR_CODE_RECOGNITION_RESUME_FAILED, + "STService recognition pause failed with: " + status)); } @Override diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index 78c61964edfd..559faf9b20de 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -3142,10 +3142,10 @@ public class SubscriptionManager { * * Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns * true). To check for permissions for non-embedded subscription as well, + * see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}. * * @param info The subscription to check. * @return whether the app is authorized to manage this subscription per its metadata. - * * @see android.telephony.TelephonyManager#hasCarrierPrivileges */ public boolean canManageSubscription(SubscriptionInfo info) { @@ -3159,12 +3159,12 @@ public class SubscriptionManager { * * Only supported for embedded subscriptions (if {@link SubscriptionInfo#isEmbedded} returns * true). To check for permissions for non-embedded subscription as well, + * see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}. * * @param info The subscription to check. * @param packageName Package name of the app to check. * * @return whether the app is authorized to manage this subscription per its access rules. - * * @see android.telephony.TelephonyManager#hasCarrierPrivileges * @hide */ diff --git a/telephony/java/android/telephony/satellite/PointingInfo.java b/telephony/java/android/telephony/satellite/PointingInfo.java index e8a8576daf58..a559b32f0021 100644 --- a/telephony/java/android/telephony/satellite/PointingInfo.java +++ b/telephony/java/android/telephony/satellite/PointingInfo.java @@ -17,7 +17,6 @@ package android.telephony.satellite; import android.annotation.NonNull; -import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -34,7 +33,7 @@ public final class PointingInfo implements Parcelable { /** * @hide */ - @UnsupportedAppUsage + public PointingInfo(float satelliteAzimuthDegrees, float satelliteElevationDegrees) { mSatelliteAzimuthDegrees = satelliteAzimuthDegrees; mSatelliteElevationDegrees = satelliteElevationDegrees; diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java index 00928904a4c4..6856cc0c5df2 100644 --- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java +++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java @@ -17,7 +17,6 @@ package android.telephony.satellite; import android.annotation.NonNull; -import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -57,7 +56,6 @@ public final class SatelliteCapabilities implements Parcelable { /** * @hide */ - @UnsupportedAppUsage public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, boolean isPointingRequired, int maxBytesPerOutgoingDatagram, @NonNull Map<Integer, AntennaPosition> antennaPositionMap) { diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagram.java b/telephony/java/android/telephony/satellite/SatelliteDatagram.java index 44f56f3d315c..d3cb8a07e4ba 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDatagram.java +++ b/telephony/java/android/telephony/satellite/SatelliteDatagram.java @@ -17,7 +17,6 @@ package android.telephony.satellite; import android.annotation.NonNull; -import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -33,7 +32,6 @@ public final class SatelliteDatagram implements Parcelable { /** * @hide */ - @UnsupportedAppUsage public SatelliteDatagram(@NonNull byte[] data) { mData = data; } diff --git a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java index d0409bf5df49..b2dec71ecb32 100644 --- a/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteDatagramCallback.java @@ -17,7 +17,6 @@ package android.telephony.satellite; import android.annotation.NonNull; -import android.compat.annotation.UnsupportedAppUsage; import java.util.function.Consumer; @@ -37,7 +36,6 @@ public interface SatelliteDatagramCallback { * 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 Consumer<Void> callback); } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 5681ab266c17..c5830b8249c8 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -23,7 +23,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresFeature; import android.annotation.RequiresPermission; -import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; @@ -37,8 +36,8 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import com.android.internal.telephony.IIntegerConsumer; -import com.android.internal.telephony.IVoidConsumer; import com.android.internal.telephony.ITelephony; +import com.android.internal.telephony.IVoidConsumer; import com.android.telephony.Rlog; import java.lang.annotation.Retention; @@ -83,7 +82,7 @@ public class SatelliteManager { * @param context The context the SatelliteManager belongs to. * @hide */ - @UnsupportedAppUsage + public SatelliteManager(@Nullable Context context) { this(context, SubscriptionManager.DEFAULT_SUBSCRIPTION_ID); } @@ -129,7 +128,7 @@ public class SatelliteManager { * {@link #requestIsSatelliteEnabled(Executor, OutcomeReceiver)}. * @hide */ - @UnsupportedAppUsage + public static final String KEY_SATELLITE_ENABLED = "satellite_enabled"; /** @@ -137,7 +136,7 @@ public class SatelliteManager { * {@link #requestIsDemoModeEnabled(Executor, OutcomeReceiver)}. * @hide */ - @UnsupportedAppUsage + public static final String KEY_DEMO_MODE_ENABLED = "demo_mode_enabled"; /** @@ -145,7 +144,7 @@ public class SatelliteManager { * {@link #requestIsSatelliteSupported(Executor, OutcomeReceiver)}. * @hide */ - @UnsupportedAppUsage + public static final String KEY_SATELLITE_SUPPORTED = "satellite_supported"; /** @@ -153,7 +152,7 @@ public class SatelliteManager { * {@link #requestSatelliteCapabilities(Executor, OutcomeReceiver)}. * @hide */ - @UnsupportedAppUsage + public static final String KEY_SATELLITE_CAPABILITIES = "satellite_capabilities"; /** @@ -161,7 +160,7 @@ public class SatelliteManager { * {@link #requestIsSatelliteProvisioned(Executor, OutcomeReceiver)}. * @hide */ - @UnsupportedAppUsage + public static final String KEY_SATELLITE_PROVISIONED = "satellite_provisioned"; /** @@ -169,7 +168,7 @@ public class SatelliteManager { * {@link #requestIsSatelliteCommunicationAllowedForCurrentLocation(Executor, OutcomeReceiver)}. * @hide */ - @UnsupportedAppUsage + public static final String KEY_SATELLITE_COMMUNICATION_ALLOWED = "satellite_communication_allowed"; @@ -178,7 +177,7 @@ public class SatelliteManager { * {@link #requestTimeForNextSatelliteVisibility(Executor, OutcomeReceiver)}. * @hide */ - @UnsupportedAppUsage + public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility"; /** @@ -389,7 +388,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode, @NonNull @CallbackExecutor Executor executor, @SatelliteError @NonNull Consumer<Integer> resultListener) { @@ -432,7 +431,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void requestIsSatelliteEnabled(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -487,7 +486,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void requestIsDemoModeEnabled(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -540,7 +539,7 @@ public class SatelliteManager { * * @throws IllegalStateException if the Telephony process is not currently available. */ - @UnsupportedAppUsage + public void requestIsSatelliteSupported(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -594,7 +593,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void requestSatelliteCapabilities(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<SatelliteCapabilities, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -787,7 +786,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void startSatelliteTransmissionUpdates(@NonNull @CallbackExecutor Executor executor, @SatelliteError @NonNull Consumer<Integer> resultListener, @NonNull SatelliteTransmissionUpdateCallback callback) { @@ -857,7 +856,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void stopSatelliteTransmissionUpdates( @NonNull SatelliteTransmissionUpdateCallback callback, @NonNull @CallbackExecutor Executor executor, @@ -913,7 +912,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void provisionSatelliteService(@NonNull String token, @NonNull byte[] provisionData, @Nullable CancellationSignal cancellationSignal, @NonNull @CallbackExecutor Executor executor, @@ -963,7 +962,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void deprovisionSatelliteService(@NonNull String token, @NonNull @CallbackExecutor Executor executor, @SatelliteError @NonNull Consumer<Integer> resultListener) { @@ -1003,7 +1002,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + @SatelliteError public int registerForSatelliteProvisionStateChanged( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteProvisionStateCallback callback) { @@ -1046,7 +1045,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void unregisterForSatelliteProvisionStateChanged( @NonNull SatelliteProvisionStateCallback callback) { Objects.requireNonNull(callback); @@ -1085,7 +1084,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void requestIsSatelliteProvisioned(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { Objects.requireNonNull(executor); @@ -1137,7 +1136,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + @SatelliteError public int registerForSatelliteModemStateChanged( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteStateCallback callback) { @@ -1177,7 +1176,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void unregisterForSatelliteModemStateChanged(@NonNull SatelliteStateCallback callback) { Objects.requireNonNull(callback); ISatelliteStateCallback internalCallback = sSatelliteStateCallbackMap.remove(callback); @@ -1212,7 +1211,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + @SatelliteError public int registerForSatelliteDatagram( @NonNull @CallbackExecutor Executor executor, @NonNull SatelliteDatagramCallback callback) { @@ -1268,7 +1267,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void unregisterForSatelliteDatagram(@NonNull SatelliteDatagramCallback callback) { Objects.requireNonNull(callback); ISatelliteDatagramCallback internalCallback = @@ -1306,7 +1305,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void pollPendingSatelliteDatagrams(@NonNull @CallbackExecutor Executor executor, @SatelliteError @NonNull Consumer<Integer> resultListener) { Objects.requireNonNull(executor); @@ -1359,7 +1358,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void sendSatelliteDatagram(@DatagramType int datagramType, @NonNull SatelliteDatagram datagram, boolean needFullScreenPointingUI, @NonNull @CallbackExecutor Executor executor, @@ -1405,7 +1404,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void requestIsSatelliteCommunicationAllowedForCurrentLocation( @NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) { @@ -1463,7 +1462,7 @@ public class SatelliteManager { * @throws IllegalStateException if the Telephony process is not currently available. */ @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION) - @UnsupportedAppUsage + public void requestTimeForNextSatelliteVisibility(@NonNull @CallbackExecutor Executor executor, @NonNull OutcomeReceiver<Duration, SatelliteException> callback) { Objects.requireNonNull(executor); diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java index 20875af94f5a..a62eb8b8a5fb 100644 --- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java @@ -16,8 +16,6 @@ package android.telephony.satellite; -import android.compat.annotation.UnsupportedAppUsage; - /** * A callback class for monitoring satellite provision state change events. * @@ -30,6 +28,5 @@ public interface SatelliteProvisionStateCallback { * @param provisioned The new provision state. {@code true} means satellite is provisioned * {@code false} means satellite is not provisioned. */ - @UnsupportedAppUsage void onSatelliteProvisionStateChanged(boolean provisioned); } diff --git a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java index 3312e3a90281..d9ecaa3467e3 100644 --- a/telephony/java/android/telephony/satellite/SatelliteStateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteStateCallback.java @@ -16,8 +16,6 @@ package android.telephony.satellite; -import android.compat.annotation.UnsupportedAppUsage; - /** * A callback class for monitoring satellite modem state change events. * @@ -28,6 +26,5 @@ public interface SatelliteStateCallback { * Called when satellite modem state changes. * @param state The new satellite modem state. */ - @UnsupportedAppUsage void onSatelliteModemStateChanged(@SatelliteManager.SatelliteModemState int state); } diff --git a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java index 17fff4480679..d4fe57a0be2e 100644 --- a/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java +++ b/telephony/java/android/telephony/satellite/SatelliteTransmissionUpdateCallback.java @@ -17,7 +17,6 @@ package android.telephony.satellite; import android.annotation.NonNull; -import android.compat.annotation.UnsupportedAppUsage; /** * A callback class for monitoring satellite position update and datagram transfer state change @@ -31,7 +30,6 @@ public interface SatelliteTransmissionUpdateCallback { * * @param pointingInfo The pointing info containing the satellite location. */ - @UnsupportedAppUsage void onSatellitePositionChanged(@NonNull PointingInfo pointingInfo); /** @@ -41,7 +39,6 @@ public interface SatelliteTransmissionUpdateCallback { * @param sendPendingCount The number of datagrams that are currently being sent. * @param errorCode If datagram transfer failed, the reason for failure. */ - @UnsupportedAppUsage void onSendDatagramStateChanged( @SatelliteManager.SatelliteDatagramTransferState int state, int sendPendingCount, @SatelliteManager.SatelliteError int errorCode); @@ -53,7 +50,6 @@ public interface SatelliteTransmissionUpdateCallback { * @param receivePendingCount The number of datagrams that are currently pending to be received. * @param errorCode If datagram transfer failed, the reason for failure. */ - @UnsupportedAppUsage void onReceiveDatagramStateChanged( @SatelliteManager.SatelliteDatagramTransferState int state, int receivePendingCount, @SatelliteManager.SatelliteError int errorCode); diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt index 4efee55b97b5..8f07f213521b 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt @@ -109,11 +109,11 @@ class OpenAppFromLockNotificationWithLockOverlayApp(flicker: FlickerTest) : override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd() /** {@inheritDoc} */ - @FlakyTest(bugId = 209599395) + @Presubmit @Test override fun navBarWindowIsAlwaysVisible() = super.navBarWindowIsAlwaysVisible() - @FlakyTest(bugId = 266730606) + @Presubmit @Test override fun entireScreenCovered() = super.entireScreenCovered() diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.java new file mode 100644 index 000000000000..6b924f335ef7 --- /dev/null +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/BoundsInfoDrawHelper.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.google.android.test.handwritingime; + +import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_EDITOR_BOUNDS; +import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_NONE; +import static com.google.android.test.handwritingime.HandwritingIme.BOUNDS_INFO_VISIBLE_LINE_BOUNDS; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.RectF; +import android.view.View; +import android.view.inputmethod.CursorAnchorInfo; +import android.view.inputmethod.EditorBoundsInfo; + +import androidx.annotation.Nullable; + +import com.android.internal.graphics.ColorUtils; + +import java.util.List; + +public class BoundsInfoDrawHelper { + private static final Paint sPaint = new Paint(); + private static final int EDITOR_BOUNDS_COLOR = + ColorUtils.setAlphaComponent(Color.DKGRAY, 128); + private static final int HANDWRITING_BOUNDS_COLOR = + ColorUtils.setAlphaComponent(Color.BLUE, 128); + private static final int VISIBLE_LINE_BOUNDS_COLOR = + ColorUtils.setAlphaComponent(Color.MAGENTA, 128); + + public static void draw(Canvas canvas, View inkView, int boundsInfoMode, + CursorAnchorInfo cursorAnchorInfo) { + if (boundsInfoMode == BOUNDS_INFO_NONE || cursorAnchorInfo == null) { + return; + } + + // The matrix in CursorAnchorInfo transforms the editor coordinates to on-screen + // coordinates. We then transform the matrix from the on-screen coordinates to the + // inkView's coordinates. So the result matrix transforms the editor coordinates + // to the inkView coordinates. + final Matrix matrix = cursorAnchorInfo.getMatrix(); + inkView.transformMatrixToLocal(matrix); + + if ((boundsInfoMode & BOUNDS_INFO_EDITOR_BOUNDS) != 0) { + drawEditorBoundsInfo(canvas, matrix, cursorAnchorInfo.getEditorBoundsInfo()); + } + + if ((boundsInfoMode & BOUNDS_INFO_VISIBLE_LINE_BOUNDS) != 0) { + drawVisibleLineBounds(canvas, matrix, cursorAnchorInfo.getVisibleLineBounds()); + } + } + + private static void setPaintForEditorBoundsInfo() { + sPaint.reset(); + sPaint.setStyle(Paint.Style.STROKE); + sPaint.setStrokeWidth(5f); + } + + private static void drawEditorBoundsInfo(Canvas canvas, Matrix matrix, + @Nullable EditorBoundsInfo editorBoundsInfo) { + if (editorBoundsInfo == null) { + return; + } + final RectF editorBounds = editorBoundsInfo.getEditorBounds(); + setPaintForEditorBoundsInfo(); + if (editorBounds != null) { + final RectF localEditorBounds = new RectF(editorBounds); + matrix.mapRect(localEditorBounds); + sPaint.setColor(EDITOR_BOUNDS_COLOR); + canvas.drawRect(localEditorBounds, sPaint); + } + + final RectF handwritingBounds = editorBoundsInfo.getHandwritingBounds(); + if (handwritingBounds != null) { + final RectF localHandwritingBounds = new RectF(handwritingBounds); + matrix.mapRect(localHandwritingBounds); + sPaint.setColor(HANDWRITING_BOUNDS_COLOR); + canvas.drawRect(localHandwritingBounds, sPaint); + } + } + + private static void setPaintForVisibleLineBounds() { + sPaint.reset(); + sPaint.setStyle(Paint.Style.STROKE); + sPaint.setStrokeWidth(2f); + sPaint.setColor(VISIBLE_LINE_BOUNDS_COLOR); + } + + private static void drawVisibleLineBounds(Canvas canvas, Matrix matrix, + List<RectF> visibleLineBounds) { + if (visibleLineBounds.isEmpty()) { + return; + } + setPaintForVisibleLineBounds(); + for (RectF lineBound : visibleLineBounds) { + matrix.mapRect(lineBound); + canvas.drawRect(lineBound, sPaint); + } + } +} diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java index 2fd236895467..8380dcf4b4a4 100644 --- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/HandwritingIme.java @@ -25,7 +25,9 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; +import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.DeleteGesture; +import android.view.inputmethod.EditorInfo; import android.view.inputmethod.HandwritingGesture; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InsertGesture; @@ -34,6 +36,7 @@ import android.view.inputmethod.RemoveSpaceGesture; import android.view.inputmethod.SelectGesture; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.CheckBox; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.Spinner; @@ -43,9 +46,6 @@ import java.util.Random; import java.util.function.IntConsumer; public class HandwritingIme extends InputMethodService { - - public static final int HEIGHT_DP = 100; - private static final int OP_NONE = 0; private static final int OP_SELECT = 1; private static final int OP_DELETE = 2; @@ -62,6 +62,12 @@ public class HandwritingIme extends InputMethodService { private Spinner mRichGestureGranularitySpinner; private PointF mRichGestureStartPoint; + static final int BOUNDS_INFO_NONE = 0; + static final int BOUNDS_INFO_VISIBLE_LINE_BOUNDS = 1; + static final int BOUNDS_INFO_EDITOR_BOUNDS = 2; + private int mBoundsInfoMode = BOUNDS_INFO_NONE; + private LinearLayout mBoundsInfoCheckBoxes; + private final IntConsumer mResultConsumer = value -> Log.d(TAG, "Gesture result: " + value); interface HandwritingFinisher { @@ -201,12 +207,7 @@ public class HandwritingIme extends InputMethodService { public View onCreateInputView() { Log.d(TAG, "onCreateInputView"); final ViewGroup view = new FrameLayout(this); - final View inner = new View(this); - final float density = getResources().getDisplayMetrics().density; - final int height = (int) (HEIGHT_DP * density); view.setPadding(0, 0, 0, 0); - view.addView(inner, new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, height)); LinearLayout layout = new LinearLayout(this); layout.setLayoutParams(new LinearLayout.LayoutParams( @@ -214,9 +215,9 @@ public class HandwritingIme extends InputMethodService { layout.setOrientation(LinearLayout.VERTICAL); layout.addView(getRichGestureActionsSpinner()); layout.addView(getRichGestureGranularitySpinner()); - + layout.addView(getBoundsInfoCheckBoxes()); + layout.setBackgroundColor(getColor(R.color.holo_green_light)); view.addView(layout); - inner.setBackgroundColor(getColor(R.color.holo_green_light)); return view; } @@ -228,7 +229,7 @@ public class HandwritingIme extends InputMethodService { mRichGestureModeSpinner = new Spinner(this); mRichGestureModeSpinner.setPadding(100, 0, 100, 0); mRichGestureModeSpinner.setTooltipText("Handwriting IME mode"); - String[] items = new String[] { + String[] items = new String[]{ "Handwriting IME - Rich gesture disabled", "Rich gesture SELECT", "Rich gesture DELETE", @@ -259,6 +260,69 @@ public class HandwritingIme extends InputMethodService { return mRichGestureModeSpinner; } + private void updateCursorAnchorInfo(int boundsInfoMode) { + final InputConnection ic = getCurrentInputConnection(); + if (ic == null) return; + + if (boundsInfoMode == BOUNDS_INFO_NONE) { + ic.requestCursorUpdates(0); + return; + } + + final int cursorUpdateMode = InputConnection.CURSOR_UPDATE_MONITOR; + int cursorUpdateFilter = 0; + if ((boundsInfoMode & BOUNDS_INFO_EDITOR_BOUNDS) != 0) { + cursorUpdateFilter |= InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS; + } + + if ((boundsInfoMode & BOUNDS_INFO_VISIBLE_LINE_BOUNDS) != 0) { + cursorUpdateFilter |= InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS; + } + ic.requestCursorUpdates(cursorUpdateMode | cursorUpdateFilter); + } + + private void updateBoundsInfoMode() { + if (mInk != null) { + mInk.setBoundsInfoMode(mBoundsInfoMode); + } + updateCursorAnchorInfo(mBoundsInfoMode); + } + + private View getBoundsInfoCheckBoxes() { + if (mBoundsInfoCheckBoxes != null) { + return mBoundsInfoCheckBoxes; + } + mBoundsInfoCheckBoxes = new LinearLayout(this); + mBoundsInfoCheckBoxes.setPadding(100, 0, 100, 0); + mBoundsInfoCheckBoxes.setOrientation(LinearLayout.HORIZONTAL); + + final CheckBox editorBoundsInfoCheckBox = new CheckBox(this); + editorBoundsInfoCheckBox.setText("EditorBoundsInfo"); + editorBoundsInfoCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + mBoundsInfoMode |= BOUNDS_INFO_EDITOR_BOUNDS; + } else { + mBoundsInfoMode &= ~BOUNDS_INFO_EDITOR_BOUNDS; + } + updateBoundsInfoMode(); + }); + + final CheckBox visibleLineBoundsInfoCheckBox = new CheckBox(this); + visibleLineBoundsInfoCheckBox.setText("VisibleLineBounds"); + visibleLineBoundsInfoCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> { + if (isChecked) { + mBoundsInfoMode |= BOUNDS_INFO_VISIBLE_LINE_BOUNDS; + } else { + mBoundsInfoMode &= ~BOUNDS_INFO_VISIBLE_LINE_BOUNDS; + } + updateBoundsInfoMode(); + }); + + mBoundsInfoCheckBoxes.addView(editorBoundsInfoCheckBox); + mBoundsInfoCheckBoxes.addView(visibleLineBoundsInfoCheckBox); + return mBoundsInfoCheckBoxes; + } + private View getRichGestureGranularitySpinner() { if (mRichGestureGranularitySpinner != null) { return mRichGestureGranularitySpinner; @@ -294,6 +358,7 @@ public class HandwritingIme extends InputMethodService { Log.d(TAG, "onPrepareStylusHandwriting "); if (mInk == null) { mInk = new InkView(this, new HandwritingFinisherImpl(), new StylusConsumer()); + mInk.setBoundsInfoMode(mBoundsInfoMode); } } @@ -323,4 +388,16 @@ public class HandwritingIme extends InputMethodService { private boolean areRichGesturesEnabled() { return mRichGestureMode != OP_NONE; } + + @Override + public void onUpdateCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) { + if (mInk != null) { + mInk.setCursorAnchorInfo(cursorAnchorInfo); + } + } + + @Override + public void onStartInput(EditorInfo attribute, boolean restarting) { + updateCursorAnchorInfo(mBoundsInfoMode); + } } diff --git a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java index e94c79ecca00..86b324cf08d9 100644 --- a/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java +++ b/tests/HandwritingIme/src/com/google/android/test/handwritingime/InkView.java @@ -26,6 +26,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowMetrics; +import android.view.inputmethod.CursorAnchorInfo; class InkView extends View { private static final long FINISH_TIMEOUT = 1500; @@ -37,6 +38,9 @@ class InkView extends View { private static final float STYLUS_MOVE_TOLERANCE = 1; private Runnable mFinishRunnable; + private CursorAnchorInfo mCursorAnchorInfo; + private int mBoundsInfoMode; + InkView(Context context, HandwritingIme.HandwritingFinisher hwController, HandwritingIme.StylusConsumer consumer) { super(context); @@ -66,6 +70,7 @@ class InkView extends View { canvas.drawPath(mPath, mPaint); canvas.drawARGB(20, 255, 50, 50); + BoundsInfoDrawHelper.draw(canvas, this, mBoundsInfoMode, mCursorAnchorInfo); } private void stylusStart(float x, float y) { @@ -156,4 +161,15 @@ class InkView extends View { return mFinishRunnable; } + void setCursorAnchorInfo(CursorAnchorInfo cursorAnchorInfo) { + mCursorAnchorInfo = cursorAnchorInfo; + invalidate(); + } + + void setBoundsInfoMode(int boundsInfoMode) { + if (boundsInfoMode != mBoundsInfoMode) { + invalidate(); + } + mBoundsInfoMode = boundsInfoMode; + } } diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index a4c48fd5f342..4fa6fbe1d4e1 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -25,7 +25,7 @@ android_test { "services.core.unboosted", "testables", "truth-prebuilt", - "ub-uiautomator", + "androidx.test.uiautomator_uiautomator", ], test_suites: ["device-tests"], } diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt index 0246426c1d33..d185ee6ae116 100644 --- a/tests/Input/src/com/android/test/input/AnrTest.kt +++ b/tests/Input/src/com/android/test/input/AnrTest.kt @@ -27,14 +27,15 @@ import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLI import android.os.SystemClock import android.provider.Settings import android.provider.Settings.Global.HIDE_ERROR_DIALOGS -import android.support.test.uiautomator.By -import android.support.test.uiautomator.UiDevice -import android.support.test.uiautomator.UiObject2 -import android.support.test.uiautomator.Until import android.testing.PollingCheck import android.view.InputDevice import android.view.MotionEvent +import androidx.test.uiautomator.By +import androidx.test.uiautomator.UiDevice +import androidx.test.uiautomator.UiObject2 +import androidx.test.uiautomator.Until + import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue diff --git a/tests/WindowAnimationJank/Android.bp b/tests/WindowAnimationJank/Android.bp index ed86aa5f90ea..8542f885d645 100644 --- a/tests/WindowAnimationJank/Android.bp +++ b/tests/WindowAnimationJank/Android.bp @@ -25,7 +25,7 @@ android_test { name: "WindowAnimationJank", srcs: ["src/**/*.java"], static_libs: [ - "ub-uiautomator", + "androidx.test.uiautomator_uiautomator", "androidx.test.janktesthelper", "junit", ], diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java b/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java index 25314644ca7e..48a359c4d0c6 100644 --- a/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java +++ b/tests/WindowAnimationJank/src/android/windowanimationjank/Utils.java @@ -18,11 +18,12 @@ import android.app.UiAutomation; import android.content.ComponentName; import android.content.Intent; import android.os.SystemClock; -import android.support.test.uiautomator.By; -import android.support.test.uiautomator.BySelector; -import android.support.test.uiautomator.UiDevice; -import android.support.test.uiautomator.UiObject2; -import android.support.test.uiautomator.Until; + +import androidx.test.uiautomator.By; +import androidx.test.uiautomator.BySelector; +import androidx.test.uiautomator.UiDevice; +import androidx.test.uiautomator.UiObject2; +import androidx.test.uiautomator.Until; /** * Set of helpers to manipulate test activities. diff --git a/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java b/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java index a8ace162c4d0..cb7c5112cba7 100644 --- a/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java +++ b/tests/WindowAnimationJank/src/android/windowanimationjank/WindowAnimationJankTestBase.java @@ -16,9 +16,8 @@ package android.windowanimationjank; -import android.support.test.uiautomator.UiDevice; - import androidx.test.jank.JankTestBase; +import androidx.test.uiautomator.UiDevice; /** * This adds additional system level jank monitor and its result is merged with primary monitor diff --git a/tests/testables/src/android/testing/TestWithLooperRule.java b/tests/testables/src/android/testing/TestWithLooperRule.java new file mode 100644 index 000000000000..99b303e0c43a --- /dev/null +++ b/tests/testables/src/android/testing/TestWithLooperRule.java @@ -0,0 +1,161 @@ +/* + * 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.testing; + +import android.testing.TestableLooper.LooperFrameworkMethod; +import android.testing.TestableLooper.RunWithLooper; + +import org.junit.internal.runners.statements.InvokeMethod; +import org.junit.rules.MethodRule; +import org.junit.runner.RunWith; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +/* + * This rule is meant to be an alternative of using AndroidTestingRunner. + * It let tests to start from background thread, and assigns mainLooper or new + * Looper for the Statement. + */ +public class TestWithLooperRule implements MethodRule { + + /* + * This rule requires to be the inner most Rule, so the next statement is RunAfters + * instead of another rule. You can set it by '@Rule(order = Integer.MAX_VALUE)' + */ + @Override + public Statement apply(Statement base, FrameworkMethod method, Object target) { + // getting testRunner check, if AndroidTestingRunning then we skip this rule + RunWith runWithAnnotation = target.getClass().getAnnotation(RunWith.class); + if (runWithAnnotation != null) { + // if AndroidTestingRunner or it's subclass is in use, do nothing + if (AndroidTestingRunner.class.isAssignableFrom(runWithAnnotation.value())) { + return base; + } + } + + // check if RunWithLooper annotation is used. If not skip this rule + RunWithLooper looperAnnotation = method.getAnnotation(RunWithLooper.class); + if (looperAnnotation == null) { + looperAnnotation = target.getClass().getAnnotation(RunWithLooper.class); + } + if (looperAnnotation == null) { + return base; + } + + try { + wrapMethodInStatement(base, method, target); + } catch (Exception e) { + throw new RuntimeException(e); + } + return base; + } + + // This method is based on JUnit4 test runner flow. It might need to be revisited when JUnit is + // upgraded + // TODO(b/277743626): use a cleaner way to wrap each statements; may require some JUnit + // patching to facilitate this. + private void wrapMethodInStatement(Statement base, FrameworkMethod method, Object target) + throws Exception { + Statement next = base; + try { + while (next != null) { + switch (next.getClass().getSimpleName()) { + case "RunAfters": + this.<List<FrameworkMethod>>wrapFieldMethodFor(next, + next.getClass(), "afters", method, target); + next = getNextStatement(next, "next"); + break; + case "RunBefores": + this.<List<FrameworkMethod>>wrapFieldMethodFor(next, + next.getClass(), "befores", method, target); + next = getNextStatement(next, "next"); + break; + case "FailOnTimeout": + // Note: withPotentialTimeout() from BlockJUnit4ClassRunner might use + // FailOnTimeout which always wraps a new thread during InvokeMethod + // method evaluation. + next = getNextStatement(next, "originalStatement"); + break; + case "InvokeMethod": + this.<FrameworkMethod>wrapFieldMethodFor(next, + InvokeMethod.class, "testMethod", method, target); + return; + default: + throw new Exception( + String.format("Unexpected Statement received: [%s]", + next.getClass().getName()) + ); + } + } + } catch (Exception e) { + throw e; + } + } + + // Wrapping the befores, afters, and InvokeMethods with LooperFrameworkMethod + // within the statement. + private <T> void wrapFieldMethodFor(Statement base, Class<?> targetClass, String fieldStr, + FrameworkMethod method, Object target) + throws NoSuchFieldException, IllegalAccessException { + Field field = targetClass.getDeclaredField(fieldStr); + field.setAccessible(true); + T fieldInstance = (T) field.get(base); + if (fieldInstance instanceof FrameworkMethod) { + field.set(base, looperWrap(method, target, (FrameworkMethod) fieldInstance)); + } else { + // Befores and afters methods lists + field.set(base, looperWrap(method, target, (List<FrameworkMethod>) fieldInstance)); + } + } + + // Retrieve the next wrapped statement based on the selected field string + private Statement getNextStatement(Statement base, String fieldStr) + throws NoSuchFieldException, IllegalAccessException { + Field nextField = base.getClass().getDeclaredField(fieldStr); + nextField.setAccessible(true); + Object value = nextField.get(base); + return value instanceof Statement ? (Statement) value : null; + } + + protected FrameworkMethod looperWrap(FrameworkMethod method, Object test, + FrameworkMethod base) { + RunWithLooper annotation = method.getAnnotation(RunWithLooper.class); + if (annotation == null) annotation = test.getClass().getAnnotation(RunWithLooper.class); + if (annotation != null) { + return LooperFrameworkMethod.get(base, annotation.setAsMainLooper(), test); + } + return base; + } + + protected List<FrameworkMethod> looperWrap(FrameworkMethod method, Object test, + List<FrameworkMethod> methods) { + RunWithLooper annotation = method.getAnnotation(RunWithLooper.class); + if (annotation == null) annotation = test.getClass().getAnnotation(RunWithLooper.class); + if (annotation != null) { + methods = new ArrayList<>(methods); + for (int i = 0; i < methods.size(); i++) { + methods.set(i, LooperFrameworkMethod.get(methods.get(i), + annotation.setAsMainLooper(), test)); + } + } + return methods; + } +} diff --git a/tests/testables/src/android/testing/TestableResources.java b/tests/testables/src/android/testing/TestableResources.java index c60f07d56d92..27d5b66b355e 100644 --- a/tests/testables/src/android/testing/TestableResources.java +++ b/tests/testables/src/android/testing/TestableResources.java @@ -59,7 +59,8 @@ public class TestableResources { * Since resource ids are unique there is a single addOverride that will override the value * whenever it is gotten regardless of which method is used (i.e. getColor or getDrawable). * </p> - * @param id The resource id to be overridden + * + * @param id The resource id to be overridden * @param value The value of the resource, null to cause a {@link Resources.NotFoundException} * when gotten. */ @@ -74,28 +75,33 @@ public class TestableResources { * cause a {@link Resources.NotFoundException} whereas removing the override will actually * switch back to returning the default/real value of the resource. * </p> - * @param id */ public void removeOverride(int id) { mOverrides.remove(id); } private Object answer(InvocationOnMock invocationOnMock) throws Throwable { - try { - int id = invocationOnMock.getArgument(0); - int index = mOverrides.indexOfKey(id); - if (index >= 0) { - Object value = mOverrides.valueAt(index); - if (value == null) throw new Resources.NotFoundException(); - return value; + // Only try to override methods with an integer first argument + if (invocationOnMock.getArguments().length > 0) { + Object argument = invocationOnMock.getArgument(0); + if (argument instanceof Integer) { + try { + int id = (Integer)argument; + int index = mOverrides.indexOfKey(id); + if (index >= 0) { + Object value = mOverrides.valueAt(index); + if (value == null) throw new Resources.NotFoundException(); + return value; + } + } catch (Resources.NotFoundException e) { + // Let through NotFoundException. + throw e; + } catch (Throwable t) { + // Generic catching for the many things that can go wrong, fall back to + // the real implementation. + Log.i(TAG, "Falling back to default resources call " + t); + } } - } catch (Resources.NotFoundException e) { - // Let through NotFoundException. - throw e; - } catch (Throwable t) { - // Generic catching for the many things that can go wrong, fall back to - // the real implementation. - Log.i(TAG, "Falling back to default resources call " + t); } return invocationOnMock.callRealMethod(); } diff --git a/tests/testables/src/android/testing/TestableSettingsProvider.java b/tests/testables/src/android/testing/TestableSettingsProvider.java index c6f18fd453c5..b850cb8e6dfd 100644 --- a/tests/testables/src/android/testing/TestableSettingsProvider.java +++ b/tests/testables/src/android/testing/TestableSettingsProvider.java @@ -72,7 +72,11 @@ public class TestableSettingsProvider extends MockContentProvider { public Bundle call(String method, String arg, Bundle extras) { // Methods are "GET_system", "GET_global", "PUT_secure", etc. - final int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.myUserId()); + int userId = extras.getInt(Settings.CALL_METHOD_USER_KEY, UserHandle.USER_CURRENT); + if (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) { + userId = UserHandle.myUserId(); + } + final String[] commands = method.split("_", 2); final String op = commands[0]; final String table = commands[1]; diff --git a/tests/testables/tests/AndroidManifest.xml b/tests/testables/tests/AndroidManifest.xml index 1731f6be4bf2..2bfb04fdb765 100644 --- a/tests/testables/tests/AndroidManifest.xml +++ b/tests/testables/tests/AndroidManifest.xml @@ -21,7 +21,7 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> - <application android:debuggable="true" android:testOnly="true"> + <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> </application> diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml deleted file mode 100644 index 6d2979423efa..000000000000 --- a/tests/testables/tests/AndroidTest.xml +++ /dev/null @@ -1,27 +0,0 @@ -<?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. - --> -<configuration description="Runs Testable Tests."> - <option name="test-tag" value="TestablesTests" /> - <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> - <option name="cleanup-apks" value="true" /> - <option name="install-arg" value="-t" /> - <option name="test-file-name" value="TestablesTests.apk" /> - </target_preparer> - <test class="com.android.tradefed.testtype.AndroidJUnitTest"> - <option name="package" value="com.android.testables"/> - </test> -</configuration> |