diff options
479 files changed, 13438 insertions, 5250 deletions
diff --git a/Android.bp b/Android.bp index cff863b44499..64d2c66f013c 100644 --- a/Android.bp +++ b/Android.bp @@ -410,6 +410,7 @@ java_defaults { "spatializer-aidl-java", "audiopolicy-aidl-java", "sounddose-aidl-java", + "modules-utils-expresslog", ], } 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/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index aef9dd058658..3aec8ba39a35 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1588,12 +1588,12 @@ public class JobSchedulerService extends com.android.server.SystemService final ArrayMap<String, List<JobInfo>> outMap = new ArrayMap<>(); synchronized (mLock) { ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid); - // Write out for loop to avoid addAll() creating an Iterator. + // Write out for loop to avoid creating an Iterator. for (int i = jobs.size() - 1; i >= 0; i--) { final JobStatus job = jobs.valueAt(i); List<JobInfo> outList = outMap.get(job.getNamespace()); if (outList == null) { - outList = new ArrayList<JobInfo>(jobs.size()); + outList = new ArrayList<>(); outMap.put(job.getNamespace(), outList); } @@ -1606,7 +1606,7 @@ public class JobSchedulerService extends com.android.server.SystemService private List<JobInfo> getPendingJobsInNamespace(int uid, @Nullable String namespace) { synchronized (mLock) { ArraySet<JobStatus> jobs = mJobs.getJobsByUid(uid); - ArrayList<JobInfo> outList = new ArrayList<JobInfo>(jobs.size()); + ArrayList<JobInfo> outList = new ArrayList<>(); // Write out for loop to avoid addAll() creating an Iterator. for (int i = jobs.size() - 1; i >= 0; i--) { final JobStatus job = jobs.valueAt(i); diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java index fc6022859f5f..ba62e96b2a32 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java @@ -31,7 +31,7 @@ import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.expresslog.Counter; +import com.android.modules.expresslog.Counter; import com.android.server.job.JobSchedulerService; import com.android.server.job.StateControllerProto; 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/boot/boot-image-profile.txt b/boot/boot-image-profile.txt index 996c3882701d..aebace57aa2c 100644 --- a/boot/boot-image-profile.txt +++ b/boot/boot-image-profile.txt @@ -33583,8 +33583,8 @@ Lcom/android/internal/dynamicanimation/animation/DynamicAnimation; Lcom/android/internal/dynamicanimation/animation/Force; Lcom/android/internal/dynamicanimation/animation/SpringAnimation; Lcom/android/internal/dynamicanimation/animation/SpringForce; -Lcom/android/internal/expresslog/Counter; -Lcom/android/internal/expresslog/Utils; +Lcom/android/modules/expresslog/Counter; +Lcom/android/modules/expresslog/Utils; Lcom/android/internal/graphics/ColorUtils$ContrastCalculator; Lcom/android/internal/graphics/ColorUtils; Lcom/android/internal/graphics/SfVsyncFrameCallbackProvider; diff --git a/boot/preloaded-classes b/boot/preloaded-classes index 21ae13474d84..4293caf57aea 100644 --- a/boot/preloaded-classes +++ b/boot/preloaded-classes @@ -10784,8 +10784,8 @@ com.android.internal.dynamicanimation.animation.DynamicAnimation com.android.internal.dynamicanimation.animation.Force com.android.internal.dynamicanimation.animation.SpringAnimation com.android.internal.dynamicanimation.animation.SpringForce -com.android.internal.expresslog.Counter -com.android.internal.expresslog.Utils +com.android.modules.expresslog.Counter +com.android.modules.expresslog.Utils com.android.internal.graphics.ColorUtils$ContrastCalculator com.android.internal.graphics.ColorUtils com.android.internal.graphics.SfVsyncFrameCallbackProvider 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/config/boot-image-profile.txt b/config/boot-image-profile.txt index 3cc990873c87..bb0748764cc8 100644 --- a/config/boot-image-profile.txt +++ b/config/boot-image-profile.txt @@ -43717,8 +43717,8 @@ Lcom/android/internal/dynamicanimation/animation/DynamicAnimation; Lcom/android/internal/dynamicanimation/animation/Force; Lcom/android/internal/dynamicanimation/animation/SpringAnimation; Lcom/android/internal/dynamicanimation/animation/SpringForce; -Lcom/android/internal/expresslog/Counter; -Lcom/android/internal/expresslog/Utils; +Lcom/android/modules/expresslog/Counter; +Lcom/android/modules/expresslog/Utils; Lcom/android/internal/graphics/ColorUtils$ContrastCalculator; Lcom/android/internal/graphics/ColorUtils; Lcom/android/internal/graphics/SfVsyncFrameCallbackProvider; diff --git a/config/preloaded-classes b/config/preloaded-classes index 8e50fe8e4e0f..1812c2bb61d6 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -10815,8 +10815,8 @@ com.android.internal.dynamicanimation.animation.DynamicAnimation com.android.internal.dynamicanimation.animation.Force com.android.internal.dynamicanimation.animation.SpringAnimation com.android.internal.dynamicanimation.animation.SpringForce -com.android.internal.expresslog.Counter -com.android.internal.expresslog.Utils +com.android.modules.expresslog.Counter +com.android.modules.expresslog.Utils com.android.internal.graphics.ColorUtils$ContrastCalculator com.android.internal.graphics.ColorUtils com.android.internal.graphics.SfVsyncFrameCallbackProvider diff --git a/core/api/current.txt b/core/api/current.txt index e10fbf20b772..288ab479c0fb 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -377,7 +377,7 @@ package android { public static final class R.attr { ctor public R.attr(); field public static final int absListViewStyle = 16842858; // 0x101006a - field public static final int accessibilityDataSensitive; + field public static final int accessibilityDataSensitive = 16844407; // 0x1010677 field public static final int accessibilityEventTypes = 16843648; // 0x1010380 field public static final int accessibilityFeedbackType = 16843650; // 0x1010382 field public static final int accessibilityFlags = 16843652; // 0x1010384 @@ -445,12 +445,12 @@ package android { field public static final int allowGameFpsOverride = 16844378; // 0x101065a field public static final int allowNativeHeapPointerTagging = 16844306; // 0x1010612 field public static final int allowParallelSyncs = 16843570; // 0x1010332 - field public static final int allowSharedIsolatedProcess; + field public static final int allowSharedIsolatedProcess = 16844413; // 0x101067d field public static final int allowSingleTap = 16843353; // 0x1010259 field public static final int allowTaskReparenting = 16843268; // 0x1010204 field public static final int allowUndo = 16843999; // 0x10104df field public static final int allowUntrustedActivityEmbedding = 16844393; // 0x1010669 - field public static final int allowUpdateOwnership; + field public static final int allowUpdateOwnership = 16844416; // 0x1010680 field public static final int alpha = 16843551; // 0x101031f field public static final int alphabeticModifiers = 16844110; // 0x101054e field public static final int alphabeticShortcut = 16843235; // 0x10101e3 @@ -556,7 +556,7 @@ package android { field public static final int canTakeScreenshot = 16844303; // 0x101060f field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230 field public static final int cantSaveState = 16844142; // 0x101056e - field public static final int capability; + field public static final int capability = 16844423; // 0x1010687 field @Deprecated public static final int capitalize = 16843113; // 0x1010169 field public static final int category = 16843752; // 0x10103e8 field public static final int centerBright = 16842956; // 0x10100cc @@ -741,7 +741,7 @@ package android { field public static final int ellipsize = 16842923; // 0x10100ab field public static final int ems = 16843096; // 0x1010158 field public static final int enableOnBackInvokedCallback = 16844396; // 0x101066c - field public static final int enableTextStylingShortcuts; + field public static final int enableTextStylingShortcuts = 16844408; // 0x1010678 field public static final int enableVrMode = 16844069; // 0x1010525 field public static final int enabled = 16842766; // 0x101000e field public static final int end = 16843996; // 0x10104dc @@ -810,7 +810,7 @@ package android { field public static final int focusableInTouchMode = 16842971; // 0x10100db field public static final int focusedByDefault = 16844100; // 0x1010544 field @Deprecated public static final int focusedMonthDateColor = 16843587; // 0x1010343 - field public static final int focusedSearchResultHighlightColor; + field public static final int focusedSearchResultHighlightColor = 16844419; // 0x1010683 field public static final int font = 16844082; // 0x1010532 field public static final int fontFamily = 16843692; // 0x10103ac field public static final int fontFeatureSettings = 16843959; // 0x10104b7 @@ -896,10 +896,10 @@ package android { field public static final int hand_secondTintMode = 16844349; // 0x101063d field public static final int handle = 16843354; // 0x101025a field public static final int handleProfiling = 16842786; // 0x1010022 - field public static final int handwritingBoundsOffsetBottom; - field public static final int handwritingBoundsOffsetLeft; - field public static final int handwritingBoundsOffsetRight; - field public static final int handwritingBoundsOffsetTop; + field public static final int handwritingBoundsOffsetBottom = 16844406; // 0x1010676 + field public static final int handwritingBoundsOffsetLeft = 16844403; // 0x1010673 + field public static final int handwritingBoundsOffsetRight = 16844405; // 0x1010675 + field public static final int handwritingBoundsOffsetTop = 16844404; // 0x1010674 field public static final int hapticFeedbackEnabled = 16843358; // 0x101025e field public static final int hardwareAccelerated = 16843475; // 0x10102d3 field public static final int hasCode = 16842764; // 0x101000c @@ -986,7 +986,7 @@ package android { field public static final int isAlwaysSyncable = 16843571; // 0x1010333 field public static final int isAsciiCapable = 16843753; // 0x10103e9 field public static final int isAuxiliary = 16843647; // 0x101037f - field public static final int isCredential; + field public static final int isCredential = 16844417; // 0x1010681 field public static final int isDefault = 16843297; // 0x1010221 field public static final int isFeatureSplit = 16844123; // 0x101055b field public static final int isGame = 16843764; // 0x10103f4 @@ -1021,8 +1021,8 @@ package android { field @Deprecated public static final int keyTextSize = 16843316; // 0x1010234 field @Deprecated public static final int keyWidth = 16843325; // 0x101023d field public static final int keyboardLayout = 16843691; // 0x10103ab - field public static final int keyboardLayoutType; - field public static final int keyboardLocale; + field public static final int keyboardLayoutType = 16844415; // 0x101067f + field public static final int keyboardLocale = 16844414; // 0x101067e field @Deprecated public static final int keyboardMode = 16843341; // 0x101024d field public static final int keyboardNavigationCluster = 16844096; // 0x1010540 field public static final int keycode = 16842949; // 0x10100c5 @@ -1263,8 +1263,8 @@ package android { field public static final int persistentDrawingCache = 16842990; // 0x10100ee field public static final int persistentWhenFeatureAvailable = 16844131; // 0x1010563 field @Deprecated public static final int phoneNumber = 16843111; // 0x1010167 - field public static final int physicalKeyboardHintLanguageTag; - field public static final int physicalKeyboardHintLayoutType; + field public static final int physicalKeyboardHintLanguageTag = 16844411; // 0x101067b + field public static final int physicalKeyboardHintLayoutType = 16844412; // 0x101067c field public static final int pivotX = 16843189; // 0x10101b5 field public static final int pivotY = 16843190; // 0x10101b6 field public static final int pointerIcon = 16844041; // 0x1010509 @@ -1354,7 +1354,7 @@ package android { field public static final int requireDeviceUnlock = 16843756; // 0x10103ec field public static final int required = 16843406; // 0x101028e field public static final int requiredAccountType = 16843734; // 0x10103d6 - field public static final int requiredDisplayCategory; + field public static final int requiredDisplayCategory = 16844409; // 0x1010679 field public static final int requiredFeature = 16844116; // 0x1010554 field public static final int requiredForAllUsers = 16843728; // 0x10103d0 field public static final int requiredNotFeature = 16844117; // 0x1010555 @@ -1422,7 +1422,7 @@ package android { field public static final int searchHintIcon = 16843988; // 0x10104d4 field public static final int searchIcon = 16843907; // 0x1010483 field public static final int searchMode = 16843221; // 0x10101d5 - field public static final int searchResultHighlightColor; + field public static final int searchResultHighlightColor = 16844418; // 0x1010682 field public static final int searchSettingsDescription = 16843402; // 0x101028a field public static final int searchSuggestAuthority = 16843222; // 0x10101d6 field public static final int searchSuggestIntentAction = 16843225; // 0x10101d9 @@ -1449,7 +1449,7 @@ package android { field public static final int sessionService = 16843837; // 0x101043d field public static final int settingsActivity = 16843301; // 0x1010225 field public static final int settingsSliceUri = 16844179; // 0x1010593 - field public static final int settingsSubtitle; + field public static final int settingsSubtitle = 16844422; // 0x1010686 field public static final int setupActivity = 16843766; // 0x10103f6 field public static final int shadowColor = 16843105; // 0x1010161 field public static final int shadowDx = 16843106; // 0x1010162 @@ -1555,7 +1555,7 @@ package android { field public static final int strokeLineJoin = 16843788; // 0x101040c field public static final int strokeMiterLimit = 16843789; // 0x101040d field public static final int strokeWidth = 16843783; // 0x1010407 - field public static final int stylusHandwritingSettingsActivity; + field public static final int stylusHandwritingSettingsActivity = 16844420; // 0x1010684 field public static final int subMenuArrow = 16844019; // 0x10104f3 field public static final int submitBackground = 16843912; // 0x1010488 field public static final int subtitle = 16843473; // 0x10102d1 @@ -1861,7 +1861,7 @@ package android { field public static final int windowMinWidthMajor = 16843606; // 0x1010356 field public static final int windowMinWidthMinor = 16843607; // 0x1010357 field public static final int windowNoDisplay = 16843294; // 0x101021e - field public static final int windowNoMoveAnimation; + field public static final int windowNoMoveAnimation = 16844421; // 0x1010685 field public static final int windowNoTitle = 16842838; // 0x1010056 field @Deprecated public static final int windowOverscan = 16843727; // 0x10103cf field public static final int windowReenterTransition = 16843951; // 0x10104af @@ -1966,18 +1966,18 @@ package android { field public static final int system_accent3_700 = 17170522; // 0x106005a field public static final int system_accent3_800 = 17170523; // 0x106005b field public static final int system_accent3_900 = 17170524; // 0x106005c - field public static final int system_background_dark; - field public static final int system_background_light; - field public static final int system_control_activated_dark; - field public static final int system_control_activated_light; - field public static final int system_control_highlight_dark; - field public static final int system_control_highlight_light; - field public static final int system_control_normal_dark; - field public static final int system_control_normal_light; - field public static final int system_error_container_dark; - field public static final int system_error_container_light; - field public static final int system_error_dark; - field public static final int system_error_light; + field public static final int system_background_dark = 17170581; // 0x1060095 + field public static final int system_background_light = 17170538; // 0x106006a + field public static final int system_control_activated_dark = 17170599; // 0x10600a7 + field public static final int system_control_activated_light = 17170556; // 0x106007c + field public static final int system_control_highlight_dark = 17170601; // 0x10600a9 + field public static final int system_control_highlight_light = 17170558; // 0x106007e + field public static final int system_control_normal_dark = 17170600; // 0x10600a8 + field public static final int system_control_normal_light = 17170557; // 0x106007d + field public static final int system_error_container_dark = 17170597; // 0x10600a5 + field public static final int system_error_container_light = 17170554; // 0x106007a + field public static final int system_error_dark = 17170595; // 0x10600a3 + field public static final int system_error_light = 17170552; // 0x1060078 field public static final int system_neutral1_0 = 17170461; // 0x106001d field public static final int system_neutral1_10 = 17170462; // 0x106001e field public static final int system_neutral1_100 = 17170464; // 0x1060020 @@ -2004,94 +2004,94 @@ package android { field public static final int system_neutral2_700 = 17170483; // 0x1060033 field public static final int system_neutral2_800 = 17170484; // 0x1060034 field public static final int system_neutral2_900 = 17170485; // 0x1060035 - field public static final int system_on_background_dark; - field public static final int system_on_background_light; - field public static final int system_on_error_container_dark; - field public static final int system_on_error_container_light; - field public static final int system_on_error_dark; - field public static final int system_on_error_light; - field public static final int system_on_primary_container_dark; - field public static final int system_on_primary_container_light; - field public static final int system_on_primary_dark; - field public static final int system_on_primary_fixed; - field public static final int system_on_primary_fixed_variant; - field public static final int system_on_primary_light; - field public static final int system_on_secondary_container_dark; - field public static final int system_on_secondary_container_light; - field public static final int system_on_secondary_dark; - field public static final int system_on_secondary_fixed; - field public static final int system_on_secondary_fixed_variant; - field public static final int system_on_secondary_light; - field public static final int system_on_surface_dark; - field public static final int system_on_surface_light; - field public static final int system_on_surface_variant_dark; - field public static final int system_on_surface_variant_light; - field public static final int system_on_tertiary_container_dark; - field public static final int system_on_tertiary_container_light; - field public static final int system_on_tertiary_dark; - field public static final int system_on_tertiary_fixed; - field public static final int system_on_tertiary_fixed_variant; - field public static final int system_on_tertiary_light; - field public static final int system_outline_dark; - field public static final int system_outline_light; - field public static final int system_outline_variant_dark; - field public static final int system_outline_variant_light; - field public static final int system_palette_key_color_neutral_dark; - field public static final int system_palette_key_color_neutral_light; - field public static final int system_palette_key_color_neutral_variant_dark; - field public static final int system_palette_key_color_neutral_variant_light; - field public static final int system_palette_key_color_primary_dark; - field public static final int system_palette_key_color_primary_light; - field public static final int system_palette_key_color_secondary_dark; - field public static final int system_palette_key_color_secondary_light; - field public static final int system_palette_key_color_tertiary_dark; - field public static final int system_palette_key_color_tertiary_light; - field public static final int system_primary_container_dark; - field public static final int system_primary_container_light; - field public static final int system_primary_dark; - field public static final int system_primary_fixed; - field public static final int system_primary_fixed_dim; - field public static final int system_primary_light; - field public static final int system_secondary_container_dark; - field public static final int system_secondary_container_light; - field public static final int system_secondary_dark; - field public static final int system_secondary_fixed; - field public static final int system_secondary_fixed_dim; - field public static final int system_secondary_light; - field public static final int system_surface_bright_dark; - field public static final int system_surface_bright_light; - field public static final int system_surface_container_dark; - field public static final int system_surface_container_high_dark; - field public static final int system_surface_container_high_light; - field public static final int system_surface_container_highest_dark; - field public static final int system_surface_container_highest_light; - field public static final int system_surface_container_light; - field public static final int system_surface_container_low_dark; - field public static final int system_surface_container_low_light; - field public static final int system_surface_container_lowest_dark; - field public static final int system_surface_container_lowest_light; - field public static final int system_surface_dark; - field public static final int system_surface_dim_dark; - field public static final int system_surface_dim_light; - field public static final int system_surface_light; - field public static final int system_surface_variant_dark; - field public static final int system_surface_variant_light; - field public static final int system_tertiary_container_dark; - field public static final int system_tertiary_container_light; - field public static final int system_tertiary_dark; - field public static final int system_tertiary_fixed; - field public static final int system_tertiary_fixed_dim; - field public static final int system_tertiary_light; - field public static final int system_text_hint_inverse_dark; - field public static final int system_text_hint_inverse_light; - field public static final int system_text_primary_inverse_dark; - field public static final int system_text_primary_inverse_disable_only_dark; - field public static final int system_text_primary_inverse_disable_only_light; - field public static final int system_text_primary_inverse_light; - field public static final int system_text_secondary_and_tertiary_inverse_dark; - field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark; - field public static final int system_text_secondary_and_tertiary_inverse_disabled_light; - field public static final int system_text_secondary_and_tertiary_inverse_light; + field public static final int system_on_background_dark = 17170582; // 0x1060096 + field public static final int system_on_background_light = 17170539; // 0x106006b + field public static final int system_on_error_container_dark = 17170598; // 0x10600a6 + field public static final int system_on_error_container_light = 17170555; // 0x106007b + field public static final int system_on_error_dark = 17170596; // 0x10600a4 + field public static final int system_on_error_light = 17170553; // 0x1060079 + field public static final int system_on_primary_container_dark = 17170570; // 0x106008a + field public static final int system_on_primary_container_light = 17170527; // 0x106005f + field public static final int system_on_primary_dark = 17170572; // 0x106008c + field public static final int system_on_primary_fixed = 17170614; // 0x10600b6 + field public static final int system_on_primary_fixed_variant = 17170615; // 0x10600b7 + field public static final int system_on_primary_light = 17170529; // 0x1060061 + field public static final int system_on_secondary_container_dark = 17170574; // 0x106008e + field public static final int system_on_secondary_container_light = 17170531; // 0x1060063 + field public static final int system_on_secondary_dark = 17170576; // 0x1060090 + field public static final int system_on_secondary_fixed = 17170618; // 0x10600ba + field public static final int system_on_secondary_fixed_variant = 17170619; // 0x10600bb + field public static final int system_on_secondary_light = 17170533; // 0x1060065 + field public static final int system_on_surface_dark = 17170584; // 0x1060098 + field public static final int system_on_surface_light = 17170541; // 0x106006d + field public static final int system_on_surface_variant_dark = 17170593; // 0x10600a1 + field public static final int system_on_surface_variant_light = 17170550; // 0x1060076 + field public static final int system_on_tertiary_container_dark = 17170578; // 0x1060092 + field public static final int system_on_tertiary_container_light = 17170535; // 0x1060067 + field public static final int system_on_tertiary_dark = 17170580; // 0x1060094 + field public static final int system_on_tertiary_fixed = 17170622; // 0x10600be + field public static final int system_on_tertiary_fixed_variant = 17170623; // 0x10600bf + field public static final int system_on_tertiary_light = 17170537; // 0x1060069 + field public static final int system_outline_dark = 17170594; // 0x10600a2 + field public static final int system_outline_light = 17170551; // 0x1060077 + field public static final int system_outline_variant_dark = 17170625; // 0x10600c1 + field public static final int system_outline_variant_light = 17170624; // 0x10600c0 + field public static final int system_palette_key_color_neutral_dark = 17170610; // 0x10600b2 + field public static final int system_palette_key_color_neutral_light = 17170567; // 0x1060087 + field public static final int system_palette_key_color_neutral_variant_dark = 17170611; // 0x10600b3 + field public static final int system_palette_key_color_neutral_variant_light = 17170568; // 0x1060088 + field public static final int system_palette_key_color_primary_dark = 17170607; // 0x10600af + field public static final int system_palette_key_color_primary_light = 17170564; // 0x1060084 + field public static final int system_palette_key_color_secondary_dark = 17170608; // 0x10600b0 + field public static final int system_palette_key_color_secondary_light = 17170565; // 0x1060085 + field public static final int system_palette_key_color_tertiary_dark = 17170609; // 0x10600b1 + field public static final int system_palette_key_color_tertiary_light = 17170566; // 0x1060086 + field public static final int system_primary_container_dark = 17170569; // 0x1060089 + field public static final int system_primary_container_light = 17170526; // 0x106005e + field public static final int system_primary_dark = 17170571; // 0x106008b + field public static final int system_primary_fixed = 17170612; // 0x10600b4 + field public static final int system_primary_fixed_dim = 17170613; // 0x10600b5 + field public static final int system_primary_light = 17170528; // 0x1060060 + field public static final int system_secondary_container_dark = 17170573; // 0x106008d + field public static final int system_secondary_container_light = 17170530; // 0x1060062 + field public static final int system_secondary_dark = 17170575; // 0x106008f + field public static final int system_secondary_fixed = 17170616; // 0x10600b8 + field public static final int system_secondary_fixed_dim = 17170617; // 0x10600b9 + field public static final int system_secondary_light = 17170532; // 0x1060064 + field public static final int system_surface_bright_dark = 17170590; // 0x106009e + field public static final int system_surface_bright_light = 17170547; // 0x1060073 + field public static final int system_surface_container_dark = 17170587; // 0x106009b + field public static final int system_surface_container_high_dark = 17170588; // 0x106009c + field public static final int system_surface_container_high_light = 17170545; // 0x1060071 + field public static final int system_surface_container_highest_dark = 17170589; // 0x106009d + field public static final int system_surface_container_highest_light = 17170546; // 0x1060072 + field public static final int system_surface_container_light = 17170544; // 0x1060070 + field public static final int system_surface_container_low_dark = 17170585; // 0x1060099 + field public static final int system_surface_container_low_light = 17170542; // 0x106006e + field public static final int system_surface_container_lowest_dark = 17170586; // 0x106009a + field public static final int system_surface_container_lowest_light = 17170543; // 0x106006f + field public static final int system_surface_dark = 17170583; // 0x1060097 + field public static final int system_surface_dim_dark = 17170591; // 0x106009f + field public static final int system_surface_dim_light = 17170548; // 0x1060074 + field public static final int system_surface_light = 17170540; // 0x106006c + field public static final int system_surface_variant_dark = 17170592; // 0x10600a0 + field public static final int system_surface_variant_light = 17170549; // 0x1060075 + field public static final int system_tertiary_container_dark = 17170577; // 0x1060091 + field public static final int system_tertiary_container_light = 17170534; // 0x1060066 + field public static final int system_tertiary_dark = 17170579; // 0x1060093 + field public static final int system_tertiary_fixed = 17170620; // 0x10600bc + field public static final int system_tertiary_fixed_dim = 17170621; // 0x10600bd + field public static final int system_tertiary_light = 17170536; // 0x1060068 + field public static final int system_text_hint_inverse_dark = 17170606; // 0x10600ae + field public static final int system_text_hint_inverse_light = 17170563; // 0x1060083 + field public static final int system_text_primary_inverse_dark = 17170602; // 0x10600aa + field public static final int system_text_primary_inverse_disable_only_dark = 17170604; // 0x10600ac + field public static final int system_text_primary_inverse_disable_only_light = 17170561; // 0x1060081 + field public static final int system_text_primary_inverse_light = 17170559; // 0x106007f + field public static final int system_text_secondary_and_tertiary_inverse_dark = 17170603; // 0x10600ab + field public static final int system_text_secondary_and_tertiary_inverse_disabled_dark = 17170605; // 0x10600ad + field public static final int system_text_secondary_and_tertiary_inverse_disabled_light = 17170562; // 0x1060082 + field public static final int system_text_secondary_and_tertiary_inverse_light = 17170560; // 0x1060080 field public static final int tab_indicator_text = 17170441; // 0x1060009 field @Deprecated public static final int tertiary_text_dark = 17170448; // 0x1060010 field @Deprecated public static final int tertiary_text_light = 17170449; // 0x1060011 @@ -2310,7 +2310,7 @@ package android { field public static final int accessibilityActionPageUp = 16908358; // 0x1020046 field public static final int accessibilityActionPressAndHold = 16908362; // 0x102004a field public static final int accessibilityActionScrollDown = 16908346; // 0x102003a - field public static final int accessibilityActionScrollInDirection; + field public static final int accessibilityActionScrollInDirection = 16908382; // 0x102005e field public static final int accessibilityActionScrollLeft = 16908345; // 0x1020039 field public static final int accessibilityActionScrollRight = 16908347; // 0x102003b field public static final int accessibilityActionScrollToPosition = 16908343; // 0x1020037 @@ -2331,7 +2331,7 @@ package android { field public static final int addToDictionary = 16908330; // 0x102002a field public static final int autofill = 16908355; // 0x1020043 field public static final int background = 16908288; // 0x1020000 - field public static final int bold; + field public static final int bold = 16908379; // 0x102005b field public static final int button1 = 16908313; // 0x1020019 field public static final int button2 = 16908314; // 0x102001a field public static final int button3 = 16908315; // 0x102001b @@ -2357,7 +2357,7 @@ package android { field public static final int inputExtractAccessories = 16908378; // 0x102005a field public static final int inputExtractAction = 16908377; // 0x1020059 field public static final int inputExtractEditText = 16908325; // 0x1020025 - field public static final int italic; + field public static final int italic = 16908380; // 0x102005c field @Deprecated public static final int keyboardView = 16908326; // 0x1020026 field public static final int list = 16908298; // 0x102000a field public static final int list_container = 16908351; // 0x102003f @@ -2389,7 +2389,7 @@ package android { field public static final int textAssist = 16908353; // 0x1020041 field public static final int title = 16908310; // 0x1020016 field public static final int toggle = 16908311; // 0x1020017 - field public static final int underline; + field public static final int underline = 16908381; // 0x102005d field public static final int undo = 16908338; // 0x1020032 field public static final int widget_frame = 16908312; // 0x1020018 } @@ -32652,7 +32652,7 @@ package android.os { field public static final int S = 31; // 0x1f field public static final int S_V2 = 32; // 0x20 field public static final int TIRAMISU = 33; // 0x21 - field public static final int UPSIDE_DOWN_CAKE = 10000; // 0x2710 + field public static final int UPSIDE_DOWN_CAKE = 34; // 0x22 } public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index fbc69e34a644..ace7d59c9a45 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -410,14 +410,14 @@ package android { field public static final int sdkVersion = 16844304; // 0x1010610 field public static final int supportsAmbientMode = 16844173; // 0x101058d field public static final int userRestriction = 16844164; // 0x1010584 - field public static final int visualQueryDetectionService; + field public static final int visualQueryDetectionService = 16844410; // 0x101067a } public static final class R.bool { - field public static final int config_enableDefaultNotes; - field public static final int config_enableDefaultNotesForWorkProfile; + field public static final int config_enableDefaultNotes = 17891338; // 0x111000a + field public static final int config_enableDefaultNotesForWorkProfile = 17891339; // 0x111000b field public static final int config_enableQrCodeScannerOnLockScreen = 17891336; // 0x1110008 - field public static final int config_safetyProtectionEnabled; + field public static final int config_safetyProtectionEnabled = 17891337; // 0x1110009 field public static final int config_sendPackageName = 17891328; // 0x1110000 field public static final int config_showDefaultAssistant = 17891329; // 0x1110001 field public static final int config_showDefaultEmergency = 17891330; // 0x1110002 @@ -430,7 +430,7 @@ package android { public static final class R.dimen { field public static final int config_restrictedIconSize = 17104903; // 0x1050007 - field public static final int config_viewConfigurationHandwritingGestureLineMargin; + field public static final int config_viewConfigurationHandwritingGestureLineMargin = 17104906; // 0x105000a } public static final class R.drawable { @@ -452,7 +452,7 @@ package android { field public static final int config_defaultCallRedirection = 17039397; // 0x1040025 field public static final int config_defaultCallScreening = 17039398; // 0x1040026 field public static final int config_defaultDialer = 17039395; // 0x1040023 - field public static final int config_defaultNotes; + field public static final int config_defaultNotes = 17039429; // 0x1040045 field public static final int config_defaultSms = 17039396; // 0x1040024 field public static final int config_devicePolicyManagement = 17039421; // 0x104003d field public static final int config_feedbackIntentExtraKey = 17039391; // 0x104001f @@ -468,10 +468,10 @@ package android { field public static final int config_systemAutomotiveCalendarSyncManager = 17039423; // 0x104003f field public static final int config_systemAutomotiveCluster = 17039400; // 0x1040028 field public static final int config_systemAutomotiveProjection = 17039401; // 0x1040029 - field public static final int config_systemCallStreaming; + field public static final int config_systemCallStreaming = 17039431; // 0x1040047 field public static final int config_systemCompanionDeviceProvider = 17039417; // 0x1040039 field public static final int config_systemContacts = 17039403; // 0x104002b - field public static final int config_systemFinancedDeviceController; + field public static final int config_systemFinancedDeviceController = 17039430; // 0x1040046 field public static final int config_systemGallery = 17039399; // 0x1040027 field public static final int config_systemNotificationIntelligence = 17039413; // 0x1040035 field public static final int config_systemSettingsIntelligence = 17039426; // 0x1040042 @@ -483,7 +483,7 @@ package android { field public static final int config_systemUi = 17039418; // 0x104003a field public static final int config_systemUiIntelligence = 17039410; // 0x1040032 field public static final int config_systemVisualIntelligence = 17039415; // 0x1040037 - field public static final int config_systemWearHealthService; + field public static final int config_systemWearHealthService = 17039428; // 0x1040044 field public static final int config_systemWellbeing = 17039408; // 0x1040030 field public static final int config_systemWifiCoexManager = 17039407; // 0x104002f field public static final int safety_protection_display_text = 17039425; // 0x1040041 @@ -10117,7 +10117,7 @@ package android.net.wifi.sharedconnectivity.app { public final class NetworkProviderInfo implements android.os.Parcelable { method public int describeContents(); method @IntRange(from=0, to=100) public int getBatteryPercentage(); - method @IntRange(from=0, to=3) public int getConnectionStrength(); + method @IntRange(from=0, to=4) public int getConnectionStrength(); method @NonNull public String getDeviceName(); method public int getDeviceType(); method @NonNull public android.os.Bundle getExtras(); @@ -10136,7 +10136,7 @@ package android.net.wifi.sharedconnectivity.app { ctor public NetworkProviderInfo.Builder(@NonNull String, @NonNull String); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo build(); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setBatteryPercentage(@IntRange(from=0, to=100) int); - method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int); + method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=4) int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceType(int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setExtras(@NonNull android.os.Bundle); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 2dfda517b495..2d9a99cf0d8c 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -8,8 +8,6 @@ package android { field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE"; - field public static final String BODY_SENSORS_WRIST_TEMPERATURE = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE"; - field public static final String BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND = "android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND"; field public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission.BRIGHTNESS_SLIDER_USAGE"; field public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS = "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS"; field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; @@ -779,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 { @@ -1651,6 +1660,11 @@ package android.hardware.soundtrigger { field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR; } + public class SoundTrigger { + field public static final int MODEL_PARAM_INVALID = -1; // 0xffffffff + field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 + } + public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable { ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int); } @@ -1663,6 +1677,19 @@ package android.hardware.soundtrigger { ctor public SoundTrigger.ModuleProperties(int, @NonNull String, @NonNull String, @NonNull String, int, @NonNull String, int, int, int, int, boolean, int, boolean, int, boolean, int); } + public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable { + ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int); + ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]); + method public int describeContents(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR; + field public final boolean allowMultipleTriggers; + field public final int audioCapabilities; + field public final boolean captureRequested; + field @NonNull public final byte[] data; + field @NonNull public final android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[] keyphrases; + } + public static class SoundTrigger.RecognitionEvent { ctor public SoundTrigger.RecognitionEvent(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[], long); } @@ -2000,6 +2027,57 @@ package android.media.metrics { } +package android.media.soundtrigger { + + public final class SoundTriggerInstrumentation { + method public void setResourceContention(boolean); + method public void triggerOnResourcesAvailable(); + method public void triggerRestart(); + } + + public static interface SoundTriggerInstrumentation.GlobalCallback { + method public default void onClientAttached(); + method public default void onClientDetached(); + method public default void onFrameworkDetached(); + method public void onModelLoaded(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelSession); + method public default void onPreempted(); + method public default void onRestarted(); + } + + public static interface SoundTriggerInstrumentation.ModelCallback { + method public default void onModelUnloaded(); + method public default void onParamSet(int, int); + method public void onRecognitionStarted(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionSession); + } + + public class SoundTriggerInstrumentation.ModelSession { + method public void clearModelCallback(); + method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.Keyphrase> getPhrases(); + method @NonNull public android.media.soundtrigger.SoundTriggerManager.Model getSoundModel(); + method public boolean isKeyphrase(); + method public void setModelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelCallback); + method public void triggerUnloadModel(); + } + + public static interface SoundTriggerInstrumentation.RecognitionCallback { + method public void onRecognitionStopped(); + } + + public class SoundTriggerInstrumentation.RecognitionSession { + method public void clearRecognitionCallback(); + method public int getAudioSession(); + method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig(); + method public void setRecognitionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionCallback); + method public void triggerAbortRecognition(); + method public void triggerRecognitionEvent(@NonNull byte[], @Nullable java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>); + } + + public final class SoundTriggerManager { + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public static android.media.soundtrigger.SoundTriggerInstrumentation attachInstrumentation(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.GlobalCallback); + } + +} + package android.media.tv { public final class TvInputManager { @@ -2024,6 +2102,14 @@ package android.media.tv.tuner { } +package android.media.voice { + + public final class KeyphraseModelManager { + method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void setModelDatabaseForTestEnabled(boolean); + } + +} + package android.net { public class NetworkPolicyManager { @@ -2944,6 +3030,7 @@ package android.service.voice { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback); method @NonNull public final java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties(); + method public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean); } public static class VoiceInteractionSession.ActivityId { @@ -3351,8 +3438,14 @@ package android.view { method @NonNull public android.hardware.input.InputDeviceIdentifier getIdentifier(); } + public abstract class InputEvent implements android.os.Parcelable { + method public abstract int getDisplayId(); + method public abstract void setDisplayId(int); + } + public class KeyEvent extends android.view.InputEvent implements android.os.Parcelable { method public static String actionToString(int); + method public final int getDisplayId(); method public final void setDisplayId(int); field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800 field public static final int LAST_KEYCODE = 316; // 0x13c diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 0293bb53d3f0..95e446dde4da 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -343,7 +343,170 @@ public abstract class ActivityManagerInternal { */ public abstract boolean hasRunningActivity(int uid, @Nullable String packageName); - public abstract void updateOomAdj(); + /** + * Oom Adj Reason: none - internal use only, do not use it. + * @hide + */ + public static final int OOM_ADJ_REASON_NONE = 0; + + /** + * Oom Adj Reason: activity changes. + * @hide + */ + public static final int OOM_ADJ_REASON_ACTIVITY = 1; + + /** + * Oom Adj Reason: finishing a broadcast receiver. + * @hide + */ + public static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2; + + /** + * Oom Adj Reason: starting a broadcast receiver. + * @hide + */ + public static final int OOM_ADJ_REASON_START_RECEIVER = 3; + + /** + * Oom Adj Reason: binding to a service. + * @hide + */ + public static final int OOM_ADJ_REASON_BIND_SERVICE = 4; + + /** + * Oom Adj Reason: unbinding from a service. + * @hide + */ + public static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5; + + /** + * Oom Adj Reason: starting a service. + * @hide + */ + public static final int OOM_ADJ_REASON_START_SERVICE = 6; + + /** + * Oom Adj Reason: connecting to a content provider. + * @hide + */ + public static final int OOM_ADJ_REASON_GET_PROVIDER = 7; + + /** + * Oom Adj Reason: disconnecting from a content provider. + * @hide + */ + public static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8; + + /** + * Oom Adj Reason: UI visibility changes. + * @hide + */ + public static final int OOM_ADJ_REASON_UI_VISIBILITY = 9; + + /** + * Oom Adj Reason: device power allowlist changes. + * @hide + */ + public static final int OOM_ADJ_REASON_ALLOWLIST = 10; + + /** + * Oom Adj Reason: starting a process. + * @hide + */ + public static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11; + + /** + * Oom Adj Reason: ending a process. + * @hide + */ + public static final int OOM_ADJ_REASON_PROCESS_END = 12; + + /** + * Oom Adj Reason: short FGS timeout. + * @hide + */ + public static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13; + + /** + * Oom Adj Reason: system initialization. + * @hide + */ + public static final int OOM_ADJ_REASON_SYSTEM_INIT = 14; + + /** + * Oom Adj Reason: backup/restore. + * @hide + */ + public static final int OOM_ADJ_REASON_BACKUP = 15; + + /** + * Oom Adj Reason: instrumented by the SHELL. + * @hide + */ + public static final int OOM_ADJ_REASON_SHELL = 16; + + /** + * Oom Adj Reason: task stack is being removed. + */ + public static final int OOM_ADJ_REASON_REMOVE_TASK = 17; + + /** + * Oom Adj Reason: uid idle. + */ + public static final int OOM_ADJ_REASON_UID_IDLE = 18; + + /** + * Oom Adj Reason: stop service. + */ + public static final int OOM_ADJ_REASON_STOP_SERVICE = 19; + + /** + * Oom Adj Reason: executing service. + */ + public static final int OOM_ADJ_REASON_EXECUTING_SERVICE = 20; + + /** + * Oom Adj Reason: background restriction changes. + */ + public static final int OOM_ADJ_REASON_RESTRICTION_CHANGE = 21; + + /** + * Oom Adj Reason: A package or its component is disabled. + */ + public static final int OOM_ADJ_REASON_COMPONENT_DISABLED = 22; + + @IntDef(prefix = {"OOM_ADJ_REASON_"}, value = { + OOM_ADJ_REASON_NONE, + OOM_ADJ_REASON_ACTIVITY, + OOM_ADJ_REASON_FINISH_RECEIVER, + OOM_ADJ_REASON_START_RECEIVER, + OOM_ADJ_REASON_BIND_SERVICE, + OOM_ADJ_REASON_UNBIND_SERVICE, + OOM_ADJ_REASON_START_SERVICE, + OOM_ADJ_REASON_GET_PROVIDER, + OOM_ADJ_REASON_REMOVE_PROVIDER, + OOM_ADJ_REASON_UI_VISIBILITY, + OOM_ADJ_REASON_ALLOWLIST, + OOM_ADJ_REASON_PROCESS_BEGIN, + OOM_ADJ_REASON_PROCESS_END, + OOM_ADJ_REASON_SHORT_FGS_TIMEOUT, + OOM_ADJ_REASON_SYSTEM_INIT, + OOM_ADJ_REASON_BACKUP, + OOM_ADJ_REASON_SHELL, + OOM_ADJ_REASON_REMOVE_TASK, + OOM_ADJ_REASON_UID_IDLE, + OOM_ADJ_REASON_STOP_SERVICE, + OOM_ADJ_REASON_EXECUTING_SERVICE, + OOM_ADJ_REASON_RESTRICTION_CHANGE, + OOM_ADJ_REASON_COMPONENT_DISABLED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface OomAdjReason {} + + /** + * Request to update oom adj. + */ + public abstract void updateOomAdj(@OomAdjReason int oomAdjReason); public abstract void updateCpuStats(); /** diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index b48a8fb73832..3312294865d6 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1450,9 +1450,8 @@ public class AppOpsManager { public static final int OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD = AppProtoEnums.APP_OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD; - /** @hide Access to wrist temperature sensors. */ - public static final int OP_BODY_SENSORS_WRIST_TEMPERATURE = - AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE; + // App op deprecated/removed. + private static final int OP_DEPRECATED_2 = AppProtoEnums.APP_OP_BODY_SENSORS_WRIST_TEMPERATURE; /** * Send an intent to launch instead of posting the notification to the status bar. @@ -1461,9 +1460,25 @@ public class AppOpsManager { */ public static final int OP_USE_FULL_SCREEN_INTENT = AppProtoEnums.APP_OP_USE_FULL_SCREEN_INTENT; + /** + * Hides camera indicator for sandboxed detection apps that directly access the service. + * + * @hide + */ + public static final int OP_CAMERA_SANDBOXED = + AppProtoEnums.APP_OP_CAMERA_SANDBOXED; + + /** + * Hides microphone indicator for sandboxed detection apps that directly access the service. + * + * @hide + */ + public static final int OP_RECORD_AUDIO_SANDBOXED = + AppProtoEnums.APP_OP_RECORD_AUDIO_SANDBOXED; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 134; + public static final int _NUM_OP = 136; /** * All app ops represented as strings. @@ -1603,8 +1618,9 @@ public class AppOpsManager { OPSTR_SYSTEM_EXEMPT_FROM_HIBERNATION, OPSTR_SYSTEM_EXEMPT_FROM_ACTIVITY_BG_START_RESTRICTION, OPSTR_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD, - OPSTR_BODY_SENSORS_WRIST_TEMPERATURE, OPSTR_USE_FULL_SCREEN_INTENT, + OPSTR_CAMERA_SANDBOXED, + OPSTR_RECORD_AUDIO_SANDBOXED }) public @interface AppOpString {} @@ -2013,6 +2029,20 @@ public class AppOpsManager { public static final String OPSTR_COARSE_LOCATION_SOURCE = "android:coarse_location_source"; /** + * Camera is being recorded in sandboxed detection process. + * + * @hide + */ + public static final String OPSTR_CAMERA_SANDBOXED = "android:camera_sandboxed"; + + /** + * Audio is being recorded in sandboxed detection process. + * + * @hide + */ + public static final String OPSTR_RECORD_AUDIO_SANDBOXED = "android:record_audio_sandboxed"; + + /** * Allow apps to create the requests to manage the media files without user confirmation. * * @see android.Manifest.permission#MANAGE_MEDIA @@ -2189,11 +2219,10 @@ public class AppOpsManager { "android:capture_consentless_bugreport_on_userdebug_build"; /** - * Access to wrist temperature body sensors. + * App op deprecated/removed. * @hide */ - public static final String OPSTR_BODY_SENSORS_WRIST_TEMPERATURE = - "android:body_sensors_wrist_temperature"; + public static final String OPSTR_DEPRECATED_2 = "android:deprecated_2"; /** * Send an intent to launch instead of posting the notification to the status bar. @@ -2311,7 +2340,6 @@ public class AppOpsManager { OP_READ_MEDIA_VISUAL_USER_SELECTED, OP_FOREGROUND_SERVICE_SPECIAL_USE, OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD, - OP_BODY_SENSORS_WRIST_TEMPERATURE, OP_USE_FULL_SCREEN_INTENT }; @@ -2731,14 +2759,15 @@ public class AppOpsManager { "CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD") .setPermission(Manifest.permission.CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD) .build(), - new AppOpInfo.Builder(OP_BODY_SENSORS_WRIST_TEMPERATURE, - OPSTR_BODY_SENSORS_WRIST_TEMPERATURE, - "BODY_SENSORS_WRIST_TEMPERATURE") - .setPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE) - .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_DEPRECATED_2, OPSTR_DEPRECATED_2, "DEPRECATED_2") + .setDefaultMode(AppOpsManager.MODE_IGNORED).build(), new AppOpInfo.Builder(OP_USE_FULL_SCREEN_INTENT, OPSTR_USE_FULL_SCREEN_INTENT, "USE_FULL_SCREEN_INTENT").setPermission(Manifest.permission.USE_FULL_SCREEN_INTENT) - .build() + .build(), + new AppOpInfo.Builder(OP_CAMERA_SANDBOXED, OPSTR_CAMERA_SANDBOXED, + "CAMERA_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_RECORD_AUDIO_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED, + "RECORD_AUDIO_SANDBOXED").setDefaultMode(AppOpsManager.MODE_ALLOWED).build() }; // The number of longs needed to form a full bitmask of app ops diff --git a/core/java/android/app/ForegroundServiceTypePolicy.java b/core/java/android/app/ForegroundServiceTypePolicy.java index be012cf8e202..c0c59a24dd8d 100644 --- a/core/java/android/app/ForegroundServiceTypePolicy.java +++ b/core/java/android/app/ForegroundServiceTypePolicy.java @@ -473,7 +473,6 @@ public abstract class ForegroundServiceTypePolicy { new ForegroundServiceTypePermissions(new ForegroundServiceTypePermission[] { new RegularPermission(Manifest.permission.ACTIVITY_RECOGNITION), new RegularPermission(Manifest.permission.BODY_SENSORS), - new RegularPermission(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE), new RegularPermission(Manifest.permission.HIGH_SAMPLING_RATE_SENSORS), }, false), FGS_TYPE_PERM_ENFORCEMENT_FLAG_HEALTH /* permissionEnforcementFlag */, diff --git a/core/java/android/app/IWallpaperManager.aidl b/core/java/android/app/IWallpaperManager.aidl index ee242635bfb2..2b1558937d21 100644 --- a/core/java/android/app/IWallpaperManager.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -220,6 +220,20 @@ interface IWallpaperManager { void notifyGoingToSleep(int x, int y, in Bundle extras); /** + * Called when the screen has been fully turned on and is visible. + * + * @hide + */ + void notifyScreenTurnedOn(int displayId); + + /** + * Called when the screen starts turning on. + * + * @hide + */ + void notifyScreenTurningOn(int displayId); + + /** * Sets the wallpaper dim amount between [0f, 1f] which would be blended with the system default * dimming. 0f doesn't add any additional dimming and 1f makes the wallpaper fully black. * 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/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index 1df860258d82..bc5f7f411af5 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -110,6 +110,9 @@ "options": [ { "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceStressTest" } ], "file_patterns": ["(/|^)VoiceInteract[^/]*"] 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/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java index 852934a808e2..629d578c7358 100644 --- a/core/java/android/credentials/ui/CreateCredentialProviderData.java +++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java @@ -19,6 +19,7 @@ package android.credentials.ui; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; @@ -35,7 +36,7 @@ import java.util.List; @TestApi public final class CreateCredentialProviderData extends ProviderData implements Parcelable { @NonNull - private final List<Entry> mSaveEntries; + private final ParceledListSlice<Entry> mSaveEntries; @Nullable private final Entry mRemoteEntry; @@ -43,13 +44,13 @@ public final class CreateCredentialProviderData extends ProviderData implements @NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries, @Nullable Entry remoteEntry) { super(providerFlattenedComponentName); - mSaveEntries = saveEntries; + mSaveEntries = new ParceledListSlice<>(saveEntries); mRemoteEntry = remoteEntry; } @NonNull public List<Entry> getSaveEntries() { - return mSaveEntries; + return mSaveEntries.getList(); } @Nullable @@ -60,9 +61,7 @@ public final class CreateCredentialProviderData extends ProviderData implements private CreateCredentialProviderData(@NonNull Parcel in) { super(in); - List<Entry> credentialEntries = new ArrayList<>(); - in.readTypedList(credentialEntries, Entry.CREATOR); - mSaveEntries = credentialEntries; + mSaveEntries = in.readParcelable(null, android.content.pm.ParceledListSlice.class); AnnotationValidations.validate(NonNull.class, null, mSaveEntries); Entry remoteEntry = in.readTypedObject(Entry.CREATOR); @@ -72,7 +71,7 @@ public final class CreateCredentialProviderData extends ProviderData implements @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeTypedList(mSaveEntries); + dest.writeParcelable(mSaveEntries, flags); dest.writeTypedObject(mRemoteEntry, flags); } diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java index e4688a84a3fb..773dee97f7fe 100644 --- a/core/java/android/credentials/ui/GetCredentialProviderData.java +++ b/core/java/android/credentials/ui/GetCredentialProviderData.java @@ -19,6 +19,7 @@ package android.credentials.ui; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; +import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; @@ -35,11 +36,11 @@ import java.util.List; @TestApi public final class GetCredentialProviderData extends ProviderData implements Parcelable { @NonNull - private final List<Entry> mCredentialEntries; + private final ParceledListSlice<Entry> mCredentialEntries; @NonNull - private final List<Entry> mActionChips; + private final ParceledListSlice<Entry> mActionChips; @NonNull - private final List<AuthenticationEntry> mAuthenticationEntries; + private final ParceledListSlice<AuthenticationEntry> mAuthenticationEntries; @Nullable private final Entry mRemoteEntry; @@ -49,25 +50,25 @@ public final class GetCredentialProviderData extends ProviderData implements Par @NonNull List<AuthenticationEntry> authenticationEntries, @Nullable Entry remoteEntry) { super(providerFlattenedComponentName); - mCredentialEntries = credentialEntries; - mActionChips = actionChips; - mAuthenticationEntries = authenticationEntries; + mCredentialEntries = new ParceledListSlice<>(credentialEntries); + mActionChips = new ParceledListSlice<>(actionChips); + mAuthenticationEntries = new ParceledListSlice<>(authenticationEntries); mRemoteEntry = remoteEntry; } @NonNull public List<Entry> getCredentialEntries() { - return mCredentialEntries; + return mCredentialEntries.getList(); } @NonNull public List<Entry> getActionChips() { - return mActionChips; + return mActionChips.getList(); } @NonNull public List<AuthenticationEntry> getAuthenticationEntries() { - return mAuthenticationEntries; + return mAuthenticationEntries.getList(); } @Nullable @@ -77,20 +78,16 @@ public final class GetCredentialProviderData extends ProviderData implements Par private GetCredentialProviderData(@NonNull Parcel in) { super(in); - - List<Entry> credentialEntries = new ArrayList<>(); - in.readTypedList(credentialEntries, Entry.CREATOR); - mCredentialEntries = credentialEntries; + mCredentialEntries = in.readParcelable(null, + android.content.pm.ParceledListSlice.class); AnnotationValidations.validate(NonNull.class, null, mCredentialEntries); - List<Entry> actionChips = new ArrayList<>(); - in.readTypedList(actionChips, Entry.CREATOR); - mActionChips = actionChips; + mActionChips = in.readParcelable(null, + android.content.pm.ParceledListSlice.class); AnnotationValidations.validate(NonNull.class, null, mActionChips); - List<AuthenticationEntry> authenticationEntries = new ArrayList<>(); - in.readTypedList(authenticationEntries, AuthenticationEntry.CREATOR); - mAuthenticationEntries = authenticationEntries; + mAuthenticationEntries = in.readParcelable(null, + android.content.pm.ParceledListSlice.class); AnnotationValidations.validate(NonNull.class, null, mAuthenticationEntries); Entry remoteEntry = in.readTypedObject(Entry.CREATOR); @@ -100,9 +97,9 @@ public final class GetCredentialProviderData extends ProviderData implements Par @Override public void writeToParcel(@NonNull Parcel dest, int flags) { super.writeToParcel(dest, flags); - dest.writeTypedList(mCredentialEntries); - dest.writeTypedList(mActionChips); - dest.writeTypedList(mAuthenticationEntries); + dest.writeParcelable(mCredentialEntries, flags); + dest.writeParcelable(mActionChips, flags); + dest.writeParcelable(mAuthenticationEntries, flags); dest.writeTypedObject(mRemoteEntry, flags); } diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index fa678fc5ee1a..2e40f6096ccb 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -142,6 +142,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan private PromptInfo mPromptInfo; private ButtonInfo mNegativeButtonInfo; private Context mContext; + private IAuthService mService; /** * Creates a builder for a {@link BiometricPrompt} dialog. @@ -212,6 +213,18 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan } /** + * @param service + * @return This builder. + * @hide + */ + @RequiresPermission(TEST_BIOMETRIC) + @NonNull + public Builder setService(@NonNull IAuthService service) { + mService = service; + return this; + } + + /** * Sets an optional title, subtitle, and/or description that will override other text when * the user is authenticating with PIN/pattern/password. Currently for internal use only. * @return This builder. @@ -472,7 +485,9 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan throw new IllegalArgumentException("Can't have both negative button behavior" + " and device credential enabled"); } - return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo); + mService = (mService == null) ? IAuthService.Stub.asInterface( + ServiceManager.getService(Context.AUTH_SERVICE)) : mService; + return new BiometricPrompt(mContext, mPromptInfo, mNegativeButtonInfo, mService); } } @@ -521,7 +536,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan public void onAuthenticationFailed() { mExecutor.execute(() -> { mAuthenticationCallback.onAuthenticationFailed(); - mIsPromptShowing = false; }); } @@ -604,12 +618,12 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan private boolean mIsPromptShowing; - private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo) { + private BiometricPrompt(Context context, PromptInfo promptInfo, ButtonInfo negativeButtonInfo, + IAuthService service) { mContext = context; mPromptInfo = promptInfo; mNegativeButtonInfo = negativeButtonInfo; - mService = IAuthService.Stub.asInterface( - ServiceManager.getService(Context.AUTH_SERVICE)); + mService = service; mIsPromptShowing = false; } diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 5feda785ece3..ad68866571e3 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -1310,6 +1310,10 @@ public abstract class CameraDevice implements AutoCloseable { * {@link Surface}, submitting a reprocess {@link CaptureRequest} with multiple * output targets will result in a {@link CaptureFailure}. * + * From Android 14 onward, {@link CaptureRequest#CONTROL_CAPTURE_INTENT} will be set to + * {@link CameraMetadata#CONTROL_CAPTURE_INTENT_STILL_CAPTURE} by default. Prior to Android 14, + * apps will need to explicitly set this key themselves. + * * @param inputResult The capture result of the output image or one of the output images used * to generate the reprocess input image for this capture request. * diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index cb1efe8c2b55..f2d8caaab0e7 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -26,6 +26,7 @@ import android.hardware.camera2.CameraCaptureSession; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraDevice; import android.hardware.camera2.CameraExtensionCharacteristics; +import android.hardware.camera2.CameraMetadata; import android.hardware.camera2.CameraOfflineSession; import android.hardware.camera2.CaptureFailure; import android.hardware.camera2.CaptureRequest; @@ -861,8 +862,13 @@ public class CameraDeviceImpl extends CameraDevice CameraMetadataNative resultMetadata = new CameraMetadataNative(inputResult.getNativeCopy()); - return new CaptureRequest.Builder(resultMetadata, /*reprocess*/true, - inputResult.getSessionId(), getId(), /*physicalCameraIdSet*/ null); + CaptureRequest.Builder builder = new CaptureRequest.Builder(resultMetadata, + /*reprocess*/true, inputResult.getSessionId(), getId(), + /*physicalCameraIdSet*/ null); + builder.set(CaptureRequest.CONTROL_CAPTURE_INTENT, + CameraMetadata.CONTROL_CAPTURE_INTENT_STILL_CAPTURE); + + return builder; } } 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/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index fa16e167f7d1..6d43ddf7fe94 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -1051,6 +1051,29 @@ public class SoundTrigger { return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]"; } } + /** + * SoundTrigger model parameter types. + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "MODEL_PARAM" }, value = { + MODEL_PARAM_INVALID, + MODEL_PARAM_THRESHOLD_FACTOR + }) + public @interface ModelParamTypes {} + + /** + * See {@link ModelParams.INVALID} + * @hide + */ + @TestApi + public static final int MODEL_PARAM_INVALID = ModelParams.INVALID; + /** + * See {@link ModelParams.THRESHOLD_FACTOR} + * @hide + */ + @TestApi + public static final int MODEL_PARAM_THRESHOLD_FACTOR = ModelParams.THRESHOLD_FACTOR; /** * Modes for key phrase recognition @@ -1450,7 +1473,8 @@ public class SoundTrigger { * * @hide */ - public static class RecognitionConfig implements Parcelable { + @TestApi + public static final class RecognitionConfig implements Parcelable { /** True if the DSP should capture the trigger sound and make it available for further * capture. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -1464,6 +1488,7 @@ public class SoundTrigger { * options for each keyphrase. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @NonNull + @SuppressLint("ArrayReturn") public final KeyphraseRecognitionExtra keyphrases[]; /** Opaque data for use by system applications who know about voice engine internals, * typically during enrollment. */ @@ -1479,8 +1504,8 @@ public class SoundTrigger { public final int audioCapabilities; public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, - @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data, - int audioCapabilities) { + @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases, + @Nullable byte[] data, int audioCapabilities) { this.captureRequested = captureRequested; this.allowMultipleTriggers = allowMultipleTriggers; this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0]; @@ -1490,7 +1515,8 @@ public class SoundTrigger { @UnsupportedAppUsage public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, - @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) { + @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases, + @Nullable byte[] data) { this(captureRequested, allowMultipleTriggers, keyphrases, data, 0); } @@ -1517,7 +1543,7 @@ public class SoundTrigger { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeByte((byte) (captureRequested ? 1 : 0)); dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0)); dest.writeTypedArray(keyphrases, flags); 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 244632a87593..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 @@ -1222,7 +1222,7 @@ public class Build { /** * Upside Down Cake. */ - public static final int UPSIDE_DOWN_CAKE = CUR_DEVELOPMENT; + public static final int UPSIDE_DOWN_CAKE = 34; } /** The type of build, like "user" or "eng". */ diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java index 63259edeae7d..ecce2ff8e960 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,19 @@ 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"; private static class IncomingHandler extends Handler { private final WeakReference<DynamicSystemClient> mWeakClient; diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index d2f9ff01ca98..59b945c9c9a4 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -2051,6 +2051,14 @@ public final class Telephony { * <P>Type: TEXT</P> */ public static final String ADDRESS = "address"; + + /** + * The subscription to which the message belongs to. Its value will be less than 0 + * if the sub id cannot be determined. + * <p>Type: INTEGER (long) </p> + * @hide + */ + public static final String SUBSCRIPTION_ID = "sub_id"; } /** @@ -2119,6 +2127,14 @@ public final class Telephony { * <P>Type: INTEGER (boolean)</P> */ public static final String ARCHIVED = "archived"; + + /** + * The subscription to which the message belongs to. Its value will be less than 0 + * if the sub id cannot be determined. + * <p>Type: INTEGER (long) </p> + * @hide + */ + public static final String SUBSCRIPTION_ID = "sub_id"; } /** @@ -2477,6 +2493,14 @@ public final class Telephony { public static final String CHARSET = "charset"; /** + * The subscription to which the message belongs to. Its value will be less than 0 + * if the sub id cannot be determined. + * <p>Type: INTEGER (long) </p> + * @hide + */ + public static final String SUBSCRIPTION_ID = "sub_id"; + + /** * Generates a Addr {@link Uri} for message, used to perform Addr table operation * for mms. * @@ -2597,6 +2621,14 @@ public final class Telephony { public static final String TEXT = "text"; /** + * The subscription to which the message belongs to. Its value will be less than 0 + * if the sub id cannot be determined. + * <p>Type: INTEGER (long) </p> + * @hide + */ + public static final String SUBSCRIPTION_ID = "sub_id"; + + /** * Generates a Part {@link Uri} for message, used to perform Part table operation * for mms. * @@ -2635,6 +2667,14 @@ public final class Telephony { * <P>Type: INTEGER (long)</P> */ public static final String SENT_TIME = "sent_time"; + + /** + * The subscription to which the message belongs to. Its value will be less than 0 + * if the sub id cannot be determined. + * <p>Type: INTEGER (long) </p> + * @hide + */ + public static final String SUBSCRIPTION_ID = "sub_id"; } /** @@ -2868,6 +2908,14 @@ public final class Telephony { * <P>Type: TEXT</P> */ public static final String INDEXED_TEXT = "index_text"; + + /** + * The subscription to which the message belongs to. Its value will be less than 0 + * if the sub id cannot be determined. + * <p>Type: INTEGER (long) </p> + * @hide + */ + public static final String SUBSCRIPTION_ID = "sub_id"; } } diff --git a/core/java/android/service/credentials/BeginCreateCredentialResponse.java b/core/java/android/service/credentials/BeginCreateCredentialResponse.java index cd53cb6afc71..df934335e49d 100644 --- a/core/java/android/service/credentials/BeginCreateCredentialResponse.java +++ b/core/java/android/service/credentials/BeginCreateCredentialResponse.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; @@ -33,7 +34,7 @@ import java.util.Objects; * Response to a {@link BeginCreateCredentialRequest}. */ public final class BeginCreateCredentialResponse implements Parcelable { - private final @NonNull List<CreateEntry> mCreateEntries; + private final @NonNull ParceledListSlice<CreateEntry> mCreateEntries; private final @Nullable RemoteEntry mRemoteCreateEntry; /** @@ -41,19 +42,19 @@ public final class BeginCreateCredentialResponse implements Parcelable { * to return. */ public BeginCreateCredentialResponse() { - this(/*createEntries=*/new ArrayList<>(), /*remoteCreateEntry=*/null); + this(/*createEntries=*/new ParceledListSlice<>(new ArrayList<>()), + /*remoteCreateEntry=*/null); } private BeginCreateCredentialResponse(@NonNull Parcel in) { - List<CreateEntry> createEntries = new ArrayList<>(); - in.readTypedList(createEntries, CreateEntry.CREATOR); - mCreateEntries = createEntries; + mCreateEntries = in.readParcelable( + null, android.content.pm.ParceledListSlice.class); mRemoteCreateEntry = in.readTypedObject(RemoteEntry.CREATOR); } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeTypedList(mCreateEntries); + dest.writeParcelable(mCreateEntries, flags); dest.writeTypedObject(mRemoteCreateEntry, flags); } @@ -76,7 +77,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { }; /* package-private */ BeginCreateCredentialResponse( - @NonNull List<CreateEntry> createEntries, + @NonNull ParceledListSlice<CreateEntry> createEntries, @Nullable RemoteEntry remoteCreateEntry) { this.mCreateEntries = createEntries; com.android.internal.util.AnnotationValidations.validate( @@ -86,7 +87,7 @@ public final class BeginCreateCredentialResponse implements Parcelable { /** Returns the list of create entries to be displayed on the UI. */ public @NonNull List<CreateEntry> getCreateEntries() { - return mCreateEntries; + return mCreateEntries.getList(); } /** Returns the remote create entry to be displayed on the UI. */ @@ -159,7 +160,9 @@ public final class BeginCreateCredentialResponse implements Parcelable { * Builds a new instance of {@link BeginCreateCredentialResponse}. */ public @NonNull BeginCreateCredentialResponse build() { - return new BeginCreateCredentialResponse(mCreateEntries, mRemoteCreateEntry); + return new BeginCreateCredentialResponse( + new ParceledListSlice<>(mCreateEntries), + mRemoteCreateEntry); } } } diff --git a/core/java/android/service/credentials/BeginGetCredentialResponse.java b/core/java/android/service/credentials/BeginGetCredentialResponse.java index e25b6869605d..5ed06ac1ade7 100644 --- a/core/java/android/service/credentials/BeginGetCredentialResponse.java +++ b/core/java/android/service/credentials/BeginGetCredentialResponse.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; +import android.content.pm.ParceledListSlice; import android.os.Parcel; import android.os.Parcelable; @@ -35,13 +36,13 @@ import java.util.Objects; */ public final class BeginGetCredentialResponse implements Parcelable { /** List of credential entries to be displayed on the UI. */ - private final @NonNull List<CredentialEntry> mCredentialEntries; + private final @NonNull ParceledListSlice<CredentialEntry> mCredentialEntries; /** List of authentication entries to be displayed on the UI. */ - private final @NonNull List<Action> mAuthenticationEntries; + private final @NonNull ParceledListSlice<Action> mAuthenticationEntries; /** List of provider actions to be displayed on the UI. */ - private final @NonNull List<Action> mActions; + private final @NonNull ParceledListSlice<Action> mActions; /** Remote credential entry to get the response from a different device. */ private final @Nullable RemoteEntry mRemoteCredentialEntry; @@ -51,31 +52,30 @@ public final class BeginGetCredentialResponse implements Parcelable { * or {@link Action} to return. */ public BeginGetCredentialResponse() { - this(/*credentialEntries=*/new ArrayList<>(), - /*authenticationActions=*/new ArrayList<>(), - /*actions=*/new ArrayList<>(), + this(/*credentialEntries=*/new ParceledListSlice<>(new ArrayList<>()), + /*authenticationEntries=*/new ParceledListSlice<>(new ArrayList<>()), + /*actions=*/new ParceledListSlice<>(new ArrayList<>()), /*remoteCredentialEntry=*/null); } - private BeginGetCredentialResponse(@NonNull List<CredentialEntry> credentialEntries, - @NonNull List<Action> authenticationEntries, @NonNull List<Action> actions, + private BeginGetCredentialResponse( + @NonNull ParceledListSlice<CredentialEntry> credentialEntries, + @NonNull ParceledListSlice<Action> authenticationEntries, + @NonNull ParceledListSlice<Action> actions, @Nullable RemoteEntry remoteCredentialEntry) { - mCredentialEntries = new ArrayList<>(credentialEntries); - mAuthenticationEntries = new ArrayList<>(authenticationEntries); - mActions = new ArrayList<>(actions); + mCredentialEntries = credentialEntries; + mAuthenticationEntries = authenticationEntries; + mActions = actions; mRemoteCredentialEntry = remoteCredentialEntry; } private BeginGetCredentialResponse(@NonNull Parcel in) { - List<CredentialEntry> credentialEntries = new ArrayList<>(); - in.readTypedList(credentialEntries, CredentialEntry.CREATOR); - mCredentialEntries = credentialEntries; - List<Action> authenticationEntries = new ArrayList<>(); - in.readTypedList(authenticationEntries, Action.CREATOR); - mAuthenticationEntries = authenticationEntries; - List<Action> actions = new ArrayList<>(); - in.readTypedList(actions, Action.CREATOR); - mActions = actions; + mCredentialEntries = in.readParcelable( + null, android.content.pm.ParceledListSlice.class); + mAuthenticationEntries = in.readParcelable( + null, android.content.pm.ParceledListSlice.class); + mActions = in.readParcelable( + null, android.content.pm.ParceledListSlice.class); mRemoteCredentialEntry = in.readTypedObject(RemoteEntry.CREATOR); } @@ -99,9 +99,9 @@ public final class BeginGetCredentialResponse implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { - dest.writeTypedList(mCredentialEntries, flags); - dest.writeTypedList(mAuthenticationEntries, flags); - dest.writeTypedList(mActions, flags); + dest.writeParcelable(mCredentialEntries, flags); + dest.writeParcelable(mAuthenticationEntries, flags); + dest.writeParcelable(mActions, flags); dest.writeTypedObject(mRemoteCredentialEntry, flags); } @@ -109,21 +109,22 @@ public final class BeginGetCredentialResponse implements Parcelable { * Returns the list of credential entries to be displayed on the UI. */ public @NonNull List<CredentialEntry> getCredentialEntries() { - return mCredentialEntries; + return mCredentialEntries.getList(); } /** * Returns the list of authentication entries to be displayed on the UI. */ public @NonNull List<Action> getAuthenticationActions() { - return mAuthenticationEntries; + return mAuthenticationEntries.getList(); } /** * Returns the list of actions to be displayed on the UI. */ public @NonNull List<Action> getActions() { - return mActions; + + return mActions.getList(); } /** @@ -268,8 +269,11 @@ public final class BeginGetCredentialResponse implements Parcelable { * Builds a {@link BeginGetCredentialResponse} instance. */ public @NonNull BeginGetCredentialResponse build() { - return new BeginGetCredentialResponse(mCredentialEntries, mAuthenticationEntries, - mActions, mRemoteCredentialEntry); + return new BeginGetCredentialResponse( + new ParceledListSlice<>(mCredentialEntries), + new ParceledListSlice<>(mAuthenticationEntries), + new ParceledListSlice<>(mActions), + mRemoteCredentialEntry); } } } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index 402da28b3c5c..828c062d955d 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -45,7 +45,6 @@ import android.provider.Settings.Global; import android.text.TextUtils; import android.text.format.DateFormat; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.PluralsMessageFormatter; import android.util.Slog; import android.util.proto.ProtoOutputStream; @@ -59,7 +58,6 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; -import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; @@ -310,86 +308,6 @@ public class ZenModeConfig implements Parcelable { return buffer.toString(); } - public Diff diff(ZenModeConfig to) { - final Diff d = new Diff(); - if (to == null) { - return d.addLine("config", "delete"); - } - if (user != to.user) { - d.addLine("user", user, to.user); - } - if (allowAlarms != to.allowAlarms) { - d.addLine("allowAlarms", allowAlarms, to.allowAlarms); - } - if (allowMedia != to.allowMedia) { - d.addLine("allowMedia", allowMedia, to.allowMedia); - } - if (allowSystem != to.allowSystem) { - d.addLine("allowSystem", allowSystem, to.allowSystem); - } - if (allowCalls != to.allowCalls) { - d.addLine("allowCalls", allowCalls, to.allowCalls); - } - if (allowReminders != to.allowReminders) { - d.addLine("allowReminders", allowReminders, to.allowReminders); - } - if (allowEvents != to.allowEvents) { - d.addLine("allowEvents", allowEvents, to.allowEvents); - } - if (allowRepeatCallers != to.allowRepeatCallers) { - d.addLine("allowRepeatCallers", allowRepeatCallers, to.allowRepeatCallers); - } - if (allowMessages != to.allowMessages) { - d.addLine("allowMessages", allowMessages, to.allowMessages); - } - if (allowCallsFrom != to.allowCallsFrom) { - d.addLine("allowCallsFrom", allowCallsFrom, to.allowCallsFrom); - } - if (allowMessagesFrom != to.allowMessagesFrom) { - d.addLine("allowMessagesFrom", allowMessagesFrom, to.allowMessagesFrom); - } - if (suppressedVisualEffects != to.suppressedVisualEffects) { - d.addLine("suppressedVisualEffects", suppressedVisualEffects, - to.suppressedVisualEffects); - } - final ArraySet<String> allRules = new ArraySet<>(); - addKeys(allRules, automaticRules); - addKeys(allRules, to.automaticRules); - final int N = allRules.size(); - for (int i = 0; i < N; i++) { - final String rule = allRules.valueAt(i); - final ZenRule fromRule = automaticRules != null ? automaticRules.get(rule) : null; - final ZenRule toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null; - ZenRule.appendDiff(d, "automaticRule[" + rule + "]", fromRule, toRule); - } - ZenRule.appendDiff(d, "manualRule", manualRule, to.manualRule); - - if (areChannelsBypassingDnd != to.areChannelsBypassingDnd) { - d.addLine("areChannelsBypassingDnd", areChannelsBypassingDnd, - to.areChannelsBypassingDnd); - } - return d; - } - - public static Diff diff(ZenModeConfig from, ZenModeConfig to) { - if (from == null) { - final Diff d = new Diff(); - if (to != null) { - d.addLine("config", "insert"); - } - return d; - } - return from.diff(to); - } - - private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) { - if (map != null) { - for (int i = 0; i < map.size(); i++) { - set.add(map.keyAt(i)); - } - } - } - public boolean isValid() { if (!isValidManualRule(manualRule)) return false; final int N = automaticRules.size(); @@ -1922,66 +1840,6 @@ public class ZenModeConfig implements Parcelable { proto.end(token); } - private static void appendDiff(Diff d, String item, ZenRule from, ZenRule to) { - if (d == null) return; - if (from == null) { - if (to != null) { - d.addLine(item, "insert"); - } - return; - } - from.appendDiff(d, item, to); - } - - private void appendDiff(Diff d, String item, ZenRule to) { - if (to == null) { - d.addLine(item, "delete"); - return; - } - if (enabled != to.enabled) { - d.addLine(item, "enabled", enabled, to.enabled); - } - if (snoozing != to.snoozing) { - d.addLine(item, "snoozing", snoozing, to.snoozing); - } - if (!Objects.equals(name, to.name)) { - d.addLine(item, "name", name, to.name); - } - if (zenMode != to.zenMode) { - d.addLine(item, "zenMode", zenMode, to.zenMode); - } - if (!Objects.equals(conditionId, to.conditionId)) { - d.addLine(item, "conditionId", conditionId, to.conditionId); - } - if (!Objects.equals(condition, to.condition)) { - d.addLine(item, "condition", condition, to.condition); - } - if (!Objects.equals(component, to.component)) { - d.addLine(item, "component", component, to.component); - } - if (!Objects.equals(configurationActivity, to.configurationActivity)) { - d.addLine(item, "configActivity", configurationActivity, to.configurationActivity); - } - if (!Objects.equals(id, to.id)) { - d.addLine(item, "id", id, to.id); - } - if (creationTime != to.creationTime) { - d.addLine(item, "creationTime", creationTime, to.creationTime); - } - if (!Objects.equals(enabler, to.enabler)) { - d.addLine(item, "enabler", enabler, to.enabler); - } - if (!Objects.equals(zenPolicy, to.zenPolicy)) { - d.addLine(item, "zenPolicy", zenPolicy, to.zenPolicy); - } - if (modified != to.modified) { - d.addLine(item, "modified", modified, to.modified); - } - if (!Objects.equals(pkg, to.pkg)) { - d.addLine(item, "pkg", pkg, to.pkg); - } - } - @Override public boolean equals(@Nullable Object o) { if (!(o instanceof ZenRule)) return false; @@ -2040,40 +1898,6 @@ public class ZenModeConfig implements Parcelable { }; } - public static class Diff { - private final ArrayList<String> lines = new ArrayList<>(); - - @Override - public String toString() { - final StringBuilder sb = new StringBuilder("Diff["); - final int N = lines.size(); - for (int i = 0; i < N; i++) { - if (i > 0) { - sb.append(",\n"); - } - sb.append(lines.get(i)); - } - return sb.append(']').toString(); - } - - private Diff addLine(String item, String action) { - lines.add(item + ":" + action); - return this; - } - - public Diff addLine(String item, String subitem, Object from, Object to) { - return addLine(item + "." + subitem, from, to); - } - - public Diff addLine(String item, Object from, Object to) { - return addLine(item, from + "->" + to); - } - - public boolean isEmpty() { - return lines.isEmpty(); - } - } - /** * Determines whether dnd behavior should mute all ringer-controlled sounds * This includes notification, ringer and system sounds diff --git a/core/java/android/service/notification/ZenModeDiff.java b/core/java/android/service/notification/ZenModeDiff.java new file mode 100644 index 000000000000..c7b89eb284b6 --- /dev/null +++ b/core/java/android/service/notification/ZenModeDiff.java @@ -0,0 +1,542 @@ +/* + * 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.service.notification; + +import android.annotation.IntDef; +import android.annotation.Nullable; +import android.util.ArrayMap; +import android.util.ArraySet; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; +import java.util.Set; + +/** + * ZenModeDiff is a utility class meant to encapsulate the diff between ZenModeConfigs and their + * subcomponents (automatic and manual ZenRules). + * @hide + */ +public class ZenModeDiff { + /** + * Enum representing whether the existence of a config or rule has changed (added or removed, + * or "none" meaning there is no change, which may either mean both null, or there exists a + * diff in fields rather than add/remove). + */ + @IntDef(value = { + NONE, + ADDED, + REMOVED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ExistenceChange{} + + public static final int NONE = 0; + public static final int ADDED = 1; + public static final int REMOVED = 2; + + /** + * Diff class representing an individual field diff. + * @param <T> The type of the field. + */ + public static class FieldDiff<T> { + private final T mFrom; + private final T mTo; + + /** + * Constructor to create a FieldDiff object with the given values. + * @param from from (old) value + * @param to to (new) value + */ + public FieldDiff(@Nullable T from, @Nullable T to) { + mFrom = from; + mTo = to; + } + + /** + * Get the "from" value + */ + public T from() { + return mFrom; + } + + /** + * Get the "to" value + */ + public T to() { + return mTo; + } + + /** + * Get the string representation of this field diff, in the form of "from->to". + */ + @Override + public String toString() { + return mFrom + "->" + mTo; + } + + /** + * Returns whether this represents an actual diff. + */ + public boolean hasDiff() { + // note that Objects.equals handles null values gracefully. + return !Objects.equals(mFrom, mTo); + } + } + + /** + * Base diff class that contains info about whether something was added, and a set of named + * fields that changed. + * Extend for diffs of specific types of objects. + */ + private abstract static class BaseDiff { + // Whether the diff was added or removed + @ExistenceChange private int mExists = NONE; + + // Map from field name to diffs for any standalone fields in the object. + private ArrayMap<String, FieldDiff> mFields = new ArrayMap<>(); + + // Functions for actually diffing objects and string representations have to be implemented + // by subclasses. + + /** + * Return whether this diff represents any changes. + */ + public abstract boolean hasDiff(); + + /** + * Return a string representation of the diff. + */ + public abstract String toString(); + + /** + * Constructor that takes the two objects meant to be compared. This constructor sets + * whether there is an existence change (added or removed). + * @param from previous Object + * @param to new Object + */ + BaseDiff(Object from, Object to) { + if (from == null) { + if (to != null) { + mExists = ADDED; + } + // If both are null, there isn't an existence change; callers/inheritors must handle + // the both null case. + } else if (to == null) { + // in this case, we know that from != null + mExists = REMOVED; + } + + // Subclasses should implement the actual diffing functionality in their own + // constructors. + } + + /** + * Add a diff for a specific field to the map. + * @param name field name + * @param diff FieldDiff object representing the diff + */ + final void addField(String name, FieldDiff diff) { + mFields.put(name, diff); + } + + /** + * Returns whether this diff represents a config being newly added. + */ + public final boolean wasAdded() { + return mExists == ADDED; + } + + /** + * Returns whether this diff represents a config being removed. + */ + public final boolean wasRemoved() { + return mExists == REMOVED; + } + + /** + * Returns whether this diff represents an object being either added or removed. + */ + public final boolean hasExistenceChange() { + return mExists != NONE; + } + + /** + * Returns whether there are any individual field diffs. + */ + public final boolean hasFieldDiffs() { + return mFields.size() > 0; + } + + /** + * Returns the diff for the specific named field if it exists + */ + public final FieldDiff getDiffForField(String name) { + return mFields.getOrDefault(name, null); + } + + /** + * Get the set of all field names with some diff. + */ + public final Set<String> fieldNamesWithDiff() { + return mFields.keySet(); + } + } + + /** + * Diff class representing a diff between two ZenModeConfigs. + */ + public static class ConfigDiff extends BaseDiff { + // Rules. Automatic rule map is keyed by the rule name. + private final ArrayMap<String, RuleDiff> mAutomaticRulesDiff = new ArrayMap<>(); + private RuleDiff mManualRuleDiff; + + // Helpers for string generation + private static final String ALLOW_CALLS_FROM_FIELD = "allowCallsFrom"; + private static final String ALLOW_MESSAGES_FROM_FIELD = "allowMessagesFrom"; + private static final String ALLOW_CONVERSATIONS_FROM_FIELD = "allowConversationsFrom"; + private static final Set<String> PEOPLE_TYPE_FIELDS = + Set.of(ALLOW_CALLS_FROM_FIELD, ALLOW_MESSAGES_FROM_FIELD); + + /** + * Create a diff that contains diffs between the "from" and "to" ZenModeConfigs. + * + * @param from previous ZenModeConfig + * @param to new ZenModeConfig + */ + public ConfigDiff(ZenModeConfig from, ZenModeConfig to) { + super(from, to); + // If both are null skip + if (from == null && to == null) { + return; + } + if (hasExistenceChange()) { + // either added or removed; return here. otherwise (they're not both null) there's + // field diffs. + return; + } + + // Now we compare all the fields, knowing there's a diff and that neither is null + if (from.user != to.user) { + addField("user", new FieldDiff<>(from.user, to.user)); + } + if (from.allowAlarms != to.allowAlarms) { + addField("allowAlarms", new FieldDiff<>(from.allowAlarms, to.allowAlarms)); + } + if (from.allowMedia != to.allowMedia) { + addField("allowMedia", new FieldDiff<>(from.allowMedia, to.allowMedia)); + } + if (from.allowSystem != to.allowSystem) { + addField("allowSystem", new FieldDiff<>(from.allowSystem, to.allowSystem)); + } + if (from.allowCalls != to.allowCalls) { + addField("allowCalls", new FieldDiff<>(from.allowCalls, to.allowCalls)); + } + if (from.allowReminders != to.allowReminders) { + addField("allowReminders", + new FieldDiff<>(from.allowReminders, to.allowReminders)); + } + if (from.allowEvents != to.allowEvents) { + addField("allowEvents", new FieldDiff<>(from.allowEvents, to.allowEvents)); + } + if (from.allowRepeatCallers != to.allowRepeatCallers) { + addField("allowRepeatCallers", + new FieldDiff<>(from.allowRepeatCallers, to.allowRepeatCallers)); + } + if (from.allowMessages != to.allowMessages) { + addField("allowMessages", + new FieldDiff<>(from.allowMessages, to.allowMessages)); + } + if (from.allowConversations != to.allowConversations) { + addField("allowConversations", + new FieldDiff<>(from.allowConversations, to.allowConversations)); + } + if (from.allowCallsFrom != to.allowCallsFrom) { + addField("allowCallsFrom", + new FieldDiff<>(from.allowCallsFrom, to.allowCallsFrom)); + } + if (from.allowMessagesFrom != to.allowMessagesFrom) { + addField("allowMessagesFrom", + new FieldDiff<>(from.allowMessagesFrom, to.allowMessagesFrom)); + } + if (from.allowConversationsFrom != to.allowConversationsFrom) { + addField("allowConversationsFrom", + new FieldDiff<>(from.allowConversationsFrom, to.allowConversationsFrom)); + } + if (from.suppressedVisualEffects != to.suppressedVisualEffects) { + addField("suppressedVisualEffects", + new FieldDiff<>(from.suppressedVisualEffects, to.suppressedVisualEffects)); + } + if (from.areChannelsBypassingDnd != to.areChannelsBypassingDnd) { + addField("areChannelsBypassingDnd", + new FieldDiff<>(from.areChannelsBypassingDnd, to.areChannelsBypassingDnd)); + } + + // Compare automatic and manual rules + final ArraySet<String> allRules = new ArraySet<>(); + addKeys(allRules, from.automaticRules); + addKeys(allRules, to.automaticRules); + final int num = allRules.size(); + for (int i = 0; i < num; i++) { + final String rule = allRules.valueAt(i); + final ZenModeConfig.ZenRule + fromRule = from.automaticRules != null ? from.automaticRules.get(rule) + : null; + final ZenModeConfig.ZenRule + toRule = to.automaticRules != null ? to.automaticRules.get(rule) : null; + RuleDiff ruleDiff = new RuleDiff(fromRule, toRule); + if (ruleDiff.hasDiff()) { + mAutomaticRulesDiff.put(rule, ruleDiff); + } + } + // If there's no diff this may turn out to be null, but that's also fine + RuleDiff manualRuleDiff = new RuleDiff(from.manualRule, to.manualRule); + if (manualRuleDiff.hasDiff()) { + mManualRuleDiff = manualRuleDiff; + } + } + + private static <T> void addKeys(ArraySet<T> set, ArrayMap<T, ?> map) { + if (map != null) { + for (int i = 0; i < map.size(); i++) { + set.add(map.keyAt(i)); + } + } + } + + /** + * Returns whether this diff object contains any diffs in any field. + */ + @Override + public boolean hasDiff() { + return hasExistenceChange() + || hasFieldDiffs() + || mManualRuleDiff != null + || mAutomaticRulesDiff.size() > 0; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("Diff["); + if (!hasDiff()) { + sb.append("no changes"); + } + + // If added or deleted, then that's just the end of it + if (hasExistenceChange()) { + if (wasAdded()) { + sb.append("added"); + } else if (wasRemoved()) { + sb.append("removed"); + } + } + + // Handle top-level field change + boolean first = true; + for (String key : fieldNamesWithDiff()) { + FieldDiff diff = getDiffForField(key); + if (diff == null) { + // this shouldn't happen, but + continue; + } + if (first) { + first = false; + } else { + sb.append(",\n"); + } + + // Some special handling for people- and conversation-type fields for readability + if (PEOPLE_TYPE_FIELDS.contains(key)) { + sb.append(key); + sb.append(":"); + sb.append(ZenModeConfig.sourceToString((int) diff.from())); + sb.append("->"); + sb.append(ZenModeConfig.sourceToString((int) diff.to())); + } else if (key.equals(ALLOW_CONVERSATIONS_FROM_FIELD)) { + sb.append(key); + sb.append(":"); + sb.append(ZenPolicy.conversationTypeToString((int) diff.from())); + sb.append("->"); + sb.append(ZenPolicy.conversationTypeToString((int) diff.to())); + } else { + sb.append(key); + sb.append(":"); + sb.append(diff); + } + } + + // manual rule + if (mManualRuleDiff != null && mManualRuleDiff.hasDiff()) { + if (first) { + first = false; + } else { + sb.append(",\n"); + } + sb.append("manualRule:"); + sb.append(mManualRuleDiff); + } + + // automatic rules + for (String rule : mAutomaticRulesDiff.keySet()) { + RuleDiff diff = mAutomaticRulesDiff.get(rule); + if (diff != null && diff.hasDiff()) { + if (first) { + first = false; + } else { + sb.append(",\n"); + } + sb.append("automaticRule["); + sb.append(rule); + sb.append("]:"); + sb.append(diff); + } + } + + return sb.append(']').toString(); + } + + /** + * Get the diff in manual rule, if it exists. + */ + public RuleDiff getManualRuleDiff() { + return mManualRuleDiff; + } + + /** + * Get the full map of automatic rule diffs, or null if there are no diffs. + */ + public ArrayMap<String, RuleDiff> getAllAutomaticRuleDiffs() { + return (mAutomaticRulesDiff.size() > 0) ? mAutomaticRulesDiff : null; + } + } + + /** + * Diff class representing a change between two ZenRules. + */ + public static class RuleDiff extends BaseDiff { + /** + * Create a RuleDiff representing the difference between two ZenRule objects. + * @param from previous ZenRule + * @param to new ZenRule + * @return The diff between the two given ZenRules + */ + public RuleDiff(ZenModeConfig.ZenRule from, ZenModeConfig.ZenRule to) { + super(from, to); + // Short-circuit the both-null case + if (from == null && to == null) { + return; + } + // Return if the diff was added or removed + if (hasExistenceChange()) { + return; + } + + if (from.enabled != to.enabled) { + addField("enabled", new FieldDiff<>(from.enabled, to.enabled)); + } + if (from.snoozing != to.snoozing) { + addField("snoozing", new FieldDiff<>(from.snoozing, to.snoozing)); + } + if (!Objects.equals(from.name, to.name)) { + addField("name", new FieldDiff<>(from.name, to.name)); + } + if (from.zenMode != to.zenMode) { + addField("zenMode", new FieldDiff<>(from.zenMode, to.zenMode)); + } + if (!Objects.equals(from.conditionId, to.conditionId)) { + addField("conditionId", new FieldDiff<>(from.conditionId, to.conditionId)); + } + if (!Objects.equals(from.condition, to.condition)) { + addField("condition", new FieldDiff<>(from.condition, to.condition)); + } + if (!Objects.equals(from.component, to.component)) { + addField("component", new FieldDiff<>(from.component, to.component)); + } + if (!Objects.equals(from.configurationActivity, to.configurationActivity)) { + addField("configurationActivity", new FieldDiff<>( + from.configurationActivity, to.configurationActivity)); + } + if (!Objects.equals(from.id, to.id)) { + addField("id", new FieldDiff<>(from.id, to.id)); + } + if (from.creationTime != to.creationTime) { + addField("creationTime", + new FieldDiff<>(from.creationTime, to.creationTime)); + } + if (!Objects.equals(from.enabler, to.enabler)) { + addField("enabler", new FieldDiff<>(from.enabler, to.enabler)); + } + if (!Objects.equals(from.zenPolicy, to.zenPolicy)) { + addField("zenPolicy", new FieldDiff<>(from.zenPolicy, to.zenPolicy)); + } + if (from.modified != to.modified) { + addField("modified", new FieldDiff<>(from.modified, to.modified)); + } + if (!Objects.equals(from.pkg, to.pkg)) { + addField("pkg", new FieldDiff<>(from.pkg, to.pkg)); + } + } + + /** + * Returns whether this object represents an actual diff. + */ + @Override + public boolean hasDiff() { + return hasExistenceChange() || hasFieldDiffs(); + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("ZenRuleDiff{"); + // If there's no diff, probably we haven't actually let this object continue existing + // but might as well handle this case. + if (!hasDiff()) { + sb.append("no changes"); + } + + // If added or deleted, then that's just the end of it + if (hasExistenceChange()) { + if (wasAdded()) { + sb.append("added"); + } else if (wasRemoved()) { + sb.append("removed"); + } + } + + // Go through all of the individual fields + boolean first = true; + for (String key : fieldNamesWithDiff()) { + FieldDiff diff = getDiffForField(key); + if (diff == null) { + // this shouldn't happen, but + continue; + } + if (first) { + first = false; + } else { + sb.append(", "); + } + + sb.append(key); + sb.append(":"); + sb.append(diff); + } + + return sb.append("}").toString(); + } + } +} 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/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index fcc64b088def..68cce4ae274a 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -50,6 +50,7 @@ import android.provider.Settings; import android.util.ArraySet; import android.util.Log; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceActionCheckCallback; import com.android.internal.app.IVoiceInteractionManagerService; @@ -200,6 +201,9 @@ public class VoiceInteractionService extends Service { private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>(); + // True if any of the createAOHD methods should use the test ST module. + @GuardedBy("mLock") + private boolean mTestModuleForAlwaysOnHotwordDetectorEnabled = false; private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) { Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString( @@ -512,14 +516,14 @@ public class VoiceInteractionService extends Service { Objects.requireNonNull(callback); return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ false, /* options= */ null, - /* sharedMemory= */ null, /* moduleProperties */ null, executor, callback); + /* sharedMemory= */ null, /* moduleProperties= */ null, executor, callback); } /** * Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor, * AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST * module to attach to. - * Use {@link listModuleProperties} to get available modules to attach to. + * Use {@link #listModuleProperties()} to get available modules to attach to. * @hide */ @TestApi @@ -645,14 +649,14 @@ public class VoiceInteractionService extends Service { Objects.requireNonNull(callback); return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ true, options, sharedMemory, - /* moduleProperties */ null, executor, callback); + /* moduleProperties= */ null, executor, callback); } /** * Same as {@link createAlwaysOnHotwordDetector(String, Locale, * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)}, * but allow explicit selection of the underlying ST module to attach to. - * Use {@link listModuleProperties} to get available modules to attach to. + * Use {@link #listModuleProperties()} to get available modules to attach to. * @hide */ @TestApi @@ -717,6 +721,10 @@ public class VoiceInteractionService extends Service { try { dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed); + // Check if we are currently overridden, and should use the test module. + if (mTestModuleForAlwaysOnHotwordDetectorEnabled) { + moduleProperties = getTestModuleProperties(); + } // If moduleProperties is null, the default STModule is used. dspDetector.initialize(options, sharedMemory, moduleProperties); } catch (Exception e) { @@ -990,6 +998,44 @@ public class VoiceInteractionService extends Service { return mKeyphraseEnrollmentInfo; } + + /** + * Configure {@link createAlwaysOnHotwordDetector(String, Locale, + * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} + * and similar overloads to utilize the test SoundTrigger module instead of the + * actual DSP module. + * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector} + * objects should attach to a test module. {@code false} if subsequently created + * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module. + * @hide + */ + @TestApi + public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) { + synchronized (mLock) { + mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled; + } + } + + /** + * Get the {@link SoundTrigger.ModuleProperties} representing the fake + * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale, + * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and + * similar overloads for test purposes. + * @return ModuleProperties to use for test purposes. + */ + private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() { + var moduleProps = listModuleProperties() + .stream() + .filter((SoundTrigger.ModuleProperties prop) + -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH)) + .findFirst() + .orElse(null); + if (moduleProps == null) { + throw new IllegalStateException("Fake ST HAL should always be available"); + } + return moduleProps; + } + /** * Checks if a given keyphrase and locale are supported to create an * {@link AlwaysOnHotwordDetector}. diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl index 4c51be0ab8d9..f1ae22eca873 100644 --- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl +++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl @@ -32,6 +32,8 @@ interface IWallpaperEngine { oneway void setDisplayPadding(in Rect padding); @UnsupportedAppUsage oneway void setVisibility(boolean visible); + oneway void onScreenTurningOn(); + oneway void onScreenTurnedOn(); oneway void setInAmbientMode(boolean inAmbientDisplay, long animationDuration); @UnsupportedAppUsage oneway void dispatchPointer(in MotionEvent event); diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 0b947fc18237..77bbeb59927a 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -168,6 +168,7 @@ public abstract class WallpaperService extends Service { private static final int MSG_ZOOM = 10100; private static final int MSG_RESIZE_PREVIEW = 10110; private static final int MSG_REPORT_SHOWN = 10150; + private static final int MSG_UPDATE_SCREEN_TURNING_ON = 10170; private static final int MSG_UPDATE_DIMMING = 10200; private static final int MSG_WALLPAPER_FLAGS_CHANGED = 10210; @@ -213,6 +214,16 @@ public abstract class WallpaperService extends Service { boolean mInitializing = true; boolean mVisible; + /** + * Whether the screen is turning on. + * After the display is powered on, brightness is initially off. It is turned on only after + * all windows have been drawn, and sysui notifies that it's ready (See + * {@link com.android.internal.policy.IKeyguardDrawnCallback}). + * As some wallpapers use visibility as a signal to start animations, this makes sure + * {@link Engine#onVisibilityChanged} is invoked only when the display is both on and + * visible (with brightness on). + */ + private boolean mIsScreenTurningOn; boolean mReportedVisible; boolean mDestroyed; // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false @@ -1018,6 +1029,7 @@ public abstract class WallpaperService extends Service { out.print(" mDestroyed="); out.println(mDestroyed); out.print(prefix); out.print("mVisible="); out.print(mVisible); out.print(" mReportedVisible="); out.println(mReportedVisible); + out.print(" mIsScreenTurningOn="); out.println(mIsScreenTurningOn); out.print(prefix); out.print("mDisplay="); out.println(mDisplay); out.print(prefix); out.print("mCreated="); out.print(mCreated); out.print(" mSurfaceCreated="); out.print(mSurfaceCreated); @@ -1549,6 +1561,13 @@ public abstract class WallpaperService extends Service { } } + void onScreenTurningOnChanged(boolean isScreenTurningOn) { + if (!mDestroyed) { + mIsScreenTurningOn = isScreenTurningOn; + reportVisibility(false); + } + } + void doVisibilityChanged(boolean visible) { if (!mDestroyed) { mVisible = visible; @@ -1565,9 +1584,10 @@ public abstract class WallpaperService extends Service { return; } if (!mDestroyed) { - mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : - mDisplay.getCommittedState(); - boolean visible = mVisible && mDisplayState != Display.STATE_OFF; + mDisplayState = + mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getCommittedState(); + boolean displayVisible = Display.isOnState(mDisplayState) && !mIsScreenTurningOn; + boolean visible = mVisible && displayVisible; if (DEBUG) { Log.v( TAG, @@ -2486,6 +2506,20 @@ public abstract class WallpaperService extends Service { } } + public void updateScreenTurningOn(boolean isScreenTurningOn) { + Message msg = mCaller.obtainMessageBO(MSG_UPDATE_SCREEN_TURNING_ON, isScreenTurningOn, + null); + mCaller.sendMessage(msg); + } + + public void onScreenTurningOn() throws RemoteException { + updateScreenTurningOn(true); + } + + public void onScreenTurnedOn() throws RemoteException { + updateScreenTurningOn(false); + } + @Override public void executeMessage(Message message) { switch (message.what) { @@ -2530,6 +2564,13 @@ public abstract class WallpaperService extends Service { + ": " + message.arg1); mEngine.doVisibilityChanged(message.arg1 != 0); break; + case MSG_UPDATE_SCREEN_TURNING_ON: + if (DEBUG) { + Log.v(TAG, + message.arg1 != 0 ? "Screen turning on" : "Screen turned on"); + } + mEngine.onScreenTurningOnChanged(/* isScreenTurningOn= */ message.arg1 != 0); + break; case MSG_WALLPAPER_OFFSETS: { mEngine.doOffsetsChanged(true); } break; diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index c3295088ef11..d87198a0dc85 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -128,6 +128,12 @@ public class FeatureFlagUtils { */ public static final String SETTINGS_ENABLE_SPA_PHASE2 = "settings_enable_spa_phase2"; + /** + * Enable the SPA metrics writing. + * @hide + */ + public static final String SETTINGS_ENABLE_SPA_METRICS = "settings_enable_spa_metrics"; + /** Flag to enable/disable adb log metrics * @hide */ @@ -226,6 +232,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD_GESTURE, "false"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_PHASE2, "false"); + DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA_METRICS, "false"); DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false"); DEFAULT_FLAGS.put(SETTINGS_SHOW_STYLUS_PREFERENCES, "true"); DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_ENROLLMENT, "false"); 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 8c4e90c81147..5dd2d82200bc 100644 --- a/core/java/android/view/Choreographer.java +++ b/core/java/android/view/Choreographer.java @@ -785,12 +785,13 @@ public final class Choreographer { DisplayEventReceiver.VsyncEventData vsyncEventData) { final long startNanos; final long frameIntervalNanos = vsyncEventData.frameInterval; + boolean resynced = false; try { + FrameTimeline timeline = mFrameData.update(frameTimeNanos, vsyncEventData); if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { - Trace.traceBegin(Trace.TRACE_TAG_VIEW, - "Choreographer#doFrame " + vsyncEventData.preferredFrameTimeline().vsyncId); + Trace.traceBegin( + Trace.TRACE_TAG_VIEW, "Choreographer#doFrame " + timeline.mVsyncId); } - mFrameData.update(frameTimeNanos, vsyncEventData); synchronized (mLock) { if (!mFrameScheduled) { traceMessage("Frame not scheduled"); @@ -828,7 +829,9 @@ public final class Choreographer { + " ms in the past."); } } - mFrameData.update(frameTimeNanos, mDisplayEventReceiver, jitterNanos); + timeline = mFrameData.update( + frameTimeNanos, mDisplayEventReceiver, jitterNanos); + resynced = true; } if (frameTimeNanos < mLastFrameTimeNanos) { @@ -860,6 +863,12 @@ public final class Choreographer { mLastVsyncEventData = vsyncEventData; } + if (resynced && Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { + String message = String.format("Choreographer#doFrame - resynced to %d in %.1fms", + timeline.mVsyncId, (timeline.mDeadlineNanos - startNanos) * 0.000001f); + Trace.traceBegin(Trace.TRACE_TAG_VIEW, message); + } + AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS); mFrameInfo.markInputHandlingStart(); @@ -875,6 +884,9 @@ public final class Choreographer { doCallbacks(Choreographer.CALLBACK_COMMIT, frameIntervalNanos); } finally { AnimationUtils.unlockAnimationClock(); + if (resynced) { + Trace.traceEnd(Trace.TRACE_TAG_VIEW); + } Trace.traceEnd(Trace.TRACE_TAG_VIEW); } @@ -1149,7 +1161,8 @@ public final class Choreographer { * Update the frame data with a {@code DisplayEventReceiver.VsyncEventData} received from * native. */ - void update(long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) { + FrameTimeline update( + long frameTimeNanos, DisplayEventReceiver.VsyncEventData vsyncEventData) { if (vsyncEventData.frameTimelines.length != mFrameTimelines.length) { throw new IllegalStateException( "Length of native frame timelines received does not match Java. Did " @@ -1164,6 +1177,7 @@ public final class Choreographer { mFrameTimelines[i].update(frameTimeline.vsyncId, frameTimeline.expectedPresentationTime, frameTimeline.deadline); } + return mFrameTimelines[mPreferredFrameTimelineIndex]; } /** @@ -1171,7 +1185,7 @@ public final class Choreographer { * * @param jitterNanos currentTime - frameTime */ - void update( + FrameTimeline update( long frameTimeNanos, DisplayEventReceiver displayEventReceiver, long jitterNanos) { int newPreferredIndex = 0; final long minimumDeadline = @@ -1192,6 +1206,7 @@ public final class Choreographer { } else { update(frameTimeNanos, newPreferredIndex); } + return mFrameTimelines[mPreferredFrameTimelineIndex]; } void update(long frameTimeNanos, int newPreferredFrameTimelineIndex) { diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java index 0b4adaeb9890..a8e68b71f5cc 100644 --- a/core/java/android/view/InputEvent.java +++ b/core/java/android/view/InputEvent.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; @@ -100,6 +101,7 @@ public abstract class InputEvent implements Parcelable { * @return The display id associated with the event. * @hide */ + @TestApi public abstract int getDisplayId(); /** @@ -107,6 +109,7 @@ public abstract class InputEvent implements Parcelable { * @param displayId * @hide */ + @TestApi public abstract void setDisplayId(int displayId); /** * Copies the event. diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index b6d9400fad5c..858da554c670 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -2100,6 +2100,7 @@ public class KeyEvent extends InputEvent implements Parcelable { } /** @hide */ + @TestApi @Override public final int getDisplayId() { return mDisplayId; diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java index cd89a561074c..16c133523303 100644 --- a/core/java/android/view/SurfaceControlViewHost.java +++ b/core/java/android/view/SurfaceControlViewHost.java @@ -303,6 +303,7 @@ 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); @@ -399,8 +400,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; } @@ -506,7 +506,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/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 2f5cd5434b89..055b5cb70562 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -6946,6 +6946,7 @@ public final class ViewRootImpl implements ViewParent, return; } final boolean needsStylusPointerIcon = event.isStylusPointer() + && event.isHoverEvent() && mInputManager.isStylusPointerIconEnabled(); if (needsStylusPointerIcon || event.isFromSource(InputDevice.SOURCE_MOUSE)) { if (event.getActionMasked() == MotionEvent.ACTION_HOVER_ENTER 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/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/BackMotionEvent.java b/core/java/android/window/BackMotionEvent.java index 8012a1c26bac..c47572313eeb 100644 --- a/core/java/android/window/BackMotionEvent.java +++ b/core/java/android/window/BackMotionEvent.java @@ -34,6 +34,8 @@ public final class BackMotionEvent implements Parcelable { private final float mTouchX; private final float mTouchY; private final float mProgress; + private final float mVelocityX; + private final float mVelocityY; @BackEvent.SwipeEdge private final int mSwipeEdge; @@ -43,19 +45,32 @@ public final class BackMotionEvent implements Parcelable { /** * Creates a new {@link BackMotionEvent} instance. * + * <p>Note: Velocity is only computed for last event, for performance reasons.</p> + * * @param touchX Absolute X location of the touch point of this event. * @param touchY Absolute Y location of the touch point of this event. * @param progress Value between 0 and 1 on how far along the back gesture is. + * @param velocityX X velocity computed from the touch point of this event. + * Value in pixels/second. {@link Float#NaN} if was not computed. + * @param velocityY Y velocity computed from the touch point of this event. + * Value in pixels/second. {@link Float#NaN} if was not computed. * @param swipeEdge Indicates which edge the swipe starts from. * @param departingAnimationTarget The remote animation target of the departing * application window. */ - public BackMotionEvent(float touchX, float touchY, float progress, + public BackMotionEvent( + float touchX, + float touchY, + float progress, + float velocityX, + float velocityY, @BackEvent.SwipeEdge int swipeEdge, @Nullable RemoteAnimationTarget departingAnimationTarget) { mTouchX = touchX; mTouchY = touchY; mProgress = progress; + mVelocityX = velocityX; + mVelocityY = velocityY; mSwipeEdge = swipeEdge; mDepartingAnimationTarget = departingAnimationTarget; } @@ -64,6 +79,8 @@ public final class BackMotionEvent implements Parcelable { mTouchX = in.readFloat(); mTouchY = in.readFloat(); mProgress = in.readFloat(); + mVelocityX = in.readFloat(); + mVelocityY = in.readFloat(); mSwipeEdge = in.readInt(); mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR); } @@ -91,11 +108,27 @@ public final class BackMotionEvent implements Parcelable { dest.writeFloat(mTouchX); dest.writeFloat(mTouchY); dest.writeFloat(mProgress); + dest.writeFloat(mVelocityX); + dest.writeFloat(mVelocityY); dest.writeInt(mSwipeEdge); dest.writeTypedObject(mDepartingAnimationTarget, flags); } /** + * Returns the absolute X location of the touch point. + */ + public float getTouchX() { + return mTouchX; + } + + /** + * Returns the absolute Y location of the touch point. + */ + public float getTouchY() { + return mTouchY; + } + + /** * Returns the progress of a {@link BackEvent}. * * @see BackEvent#getProgress() @@ -106,17 +139,21 @@ public final class BackMotionEvent implements Parcelable { } /** - * Returns the absolute X location of the touch point. + * Returns the X velocity computed from the touch point. + * + * @return value in pixels/second or {@link Float#NaN} if was not computed. */ - public float getTouchX() { - return mTouchX; + public float getVelocityX() { + return mVelocityX; } /** - * Returns the absolute Y location of the touch point. + * Returns the Y velocity computed from the touch point. + * + * @return value in pixels/second or {@link Float#NaN} if was not computed. */ - public float getTouchY() { - return mTouchY; + public float getVelocityY() { + return mVelocityY; } /** @@ -143,6 +180,8 @@ public final class BackMotionEvent implements Parcelable { + "mTouchX=" + mTouchX + ", mTouchY=" + mTouchY + ", mProgress=" + mProgress + + ", mVelocityX=" + mVelocityX + + ", mVelocityY=" + mVelocityY + ", mSwipeEdge" + mSwipeEdge + ", mDepartingAnimationTarget" + mDepartingAnimationTarget + "}"; 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/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl index ab7f602e2dfc..ed751cb481c5 100644 --- a/core/java/com/android/internal/app/ISoundTriggerService.aidl +++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl @@ -16,8 +16,9 @@ package com.android.internal.app; -import android.media.permission.Identity; import android.hardware.soundtrigger.SoundTrigger; +import android.media.permission.Identity; +import android.media.soundtrigger_middleware.ISoundTriggerInjection; import com.android.internal.app.ISoundTriggerSession; /** @@ -74,4 +75,8 @@ interface ISoundTriggerService { */ List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity); + /** + * Attach an HAL injection interface. + */ + void attachInjection(ISoundTriggerInjection injection); } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 6b40d9873fbb..24d5afc42d8f 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -96,6 +96,21 @@ interface IVoiceInteractionManagerService { * @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES */ int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale); + + /** + * Override the persistent enrolled model database with an in-memory + * fake for testing purposes. + * + * @param enabled - {@code true} to enable the test database. {@code false} to enable + * the real, persistent database. + * @param token - IBinder used to register a death listener to clean-up the override + * if tests do not clean up gracefully. + */ + @EnforcePermission("MANAGE_VOICE_KEYPHRASES") + @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(" + + "android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)") + void setModelDatabaseForTestEnabled(boolean enabled, IBinder token); + /** * Indicates if there's a keyphrase sound model available for the given keyphrase ID and the * user ID of the caller. @@ -106,6 +121,7 @@ interface IVoiceInteractionManagerService { * @param bcp47Locale The BCP47 language tag for the keyphrase's locale. */ boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale); + /** * Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale, * and the user ID of the caller. diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java deleted file mode 100644 index 4a46d91efbf0..000000000000 --- a/core/java/com/android/internal/expresslog/Counter.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.expresslog; - -import android.annotation.NonNull; - -import com.android.internal.util.FrameworkStatsLog; - -/** Counter encapsulates StatsD write API calls */ -public final class Counter { - - // Not instantiable. - private Counter() {} - - /** - * Increments Telemetry Express Counter metric by 1 - * @param metricId to log, no-op if metricId is not defined in the TeX catalog - * @hide - */ - public static void logIncrement(@NonNull String metricId) { - logIncrement(metricId, 1); - } - - /** - * Increments Telemetry Express Counter metric by 1 - * @param metricId to log, no-op if metricId is not defined in the TeX catalog - * @param uid used as a dimension for the count metric - * @hide - */ - public static void logIncrementWithUid(@NonNull String metricId, int uid) { - logIncrementWithUid(metricId, uid, 1); - } - - /** - * Increments Telemetry Express Counter metric by arbitrary value - * @param metricId to log, no-op if metricId is not defined in the TeX catalog - * @param amount to increment counter - * @hide - */ - public static void logIncrement(@NonNull String metricId, long amount) { - final long metricIdHash = Utils.hashString(metricId); - FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount); - } - - /** - * Increments Telemetry Express Counter metric by arbitrary value - * @param metricId to log, no-op if metricId is not defined in the TeX catalog - * @param uid used as a dimension for the count metric - * @param amount to increment counter - * @hide - */ - public static void logIncrementWithUid(@NonNull String metricId, int uid, long amount) { - final long metricIdHash = Utils.hashString(metricId); - FrameworkStatsLog.write( - FrameworkStatsLog.EXPRESS_UID_EVENT_REPORTED, metricIdHash, amount, uid); - } -} diff --git a/core/java/com/android/internal/expresslog/Histogram.java b/core/java/com/android/internal/expresslog/Histogram.java deleted file mode 100644 index 2fe784a5a855..000000000000 --- a/core/java/com/android/internal/expresslog/Histogram.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.internal.expresslog; - -import android.annotation.FloatRange; -import android.annotation.IntRange; -import android.annotation.NonNull; - -import com.android.internal.util.FrameworkStatsLog; - -import java.util.Arrays; - -/** Histogram encapsulates StatsD write API calls */ -public final class Histogram { - - private final long mMetricIdHash; - private final BinOptions mBinOptions; - - /** - * Creates Histogram metric logging wrapper - * - * @param metricId to log, logging will be no-op if metricId is not defined in the TeX catalog - * @param binOptions to calculate bin index for samples - * @hide - */ - public Histogram(@NonNull String metricId, @NonNull BinOptions binOptions) { - mMetricIdHash = Utils.hashString(metricId); - mBinOptions = binOptions; - } - - /** - * Logs increment sample count for automatically calculated bin - * - * @param sample value - * @hide - */ - public void logSample(float sample) { - final int binIndex = mBinOptions.getBinForSample(sample); - FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_HISTOGRAM_SAMPLE_REPORTED, mMetricIdHash, - /*count*/ 1, binIndex); - } - - /** - * Logs increment sample count for automatically calculated bin - * - * @param uid used as a dimension for the count metric - * @param sample value - * @hide - */ - public void logSampleWithUid(int uid, float sample) { - final int binIndex = mBinOptions.getBinForSample(sample); - FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_UID_HISTOGRAM_SAMPLE_REPORTED, - mMetricIdHash, /*count*/ 1, binIndex, uid); - } - - /** Used by Histogram to map data sample to corresponding bin */ - public interface BinOptions { - /** - * Returns bins count to be used by a histogram - * - * @return bins count used to initialize Options, including overflow & underflow bins - * @hide - */ - int getBinsCount(); - - /** - * Returns bin index for the input sample value - * index == 0 stands for underflow - * index == getBinsCount() - 1 stands for overflow - * - * @return zero based index - * @hide - */ - int getBinForSample(float sample); - } - - /** Used by Histogram to map data sample to corresponding bin for uniform bins */ - public static final class UniformOptions implements BinOptions { - - private final int mBinCount; - private final float mMinValue; - private final float mExclusiveMaxValue; - private final float mBinSize; - - /** - * Creates options for uniform (linear) sized bins - * - * @param binCount amount of histogram bins. 2 bin indexes will be calculated - * automatically to represent underflow & overflow bins - * @param minValue is included in the first bin, values less than minValue - * go to underflow bin - * @param exclusiveMaxValue is included in the overflow bucket. For accurate - * measure up to kMax, then exclusiveMaxValue - * should be set to kMax + 1 - * @hide - */ - public UniformOptions(@IntRange(from = 1) int binCount, float minValue, - float exclusiveMaxValue) { - if (binCount < 1) { - throw new IllegalArgumentException("Bin count should be positive number"); - } - - if (exclusiveMaxValue <= minValue) { - throw new IllegalArgumentException("Bins range invalid (maxValue < minValue)"); - } - - mMinValue = minValue; - mExclusiveMaxValue = exclusiveMaxValue; - mBinSize = (mExclusiveMaxValue - minValue) / binCount; - - // Implicitly add 2 for the extra underflow & overflow bins - mBinCount = binCount + 2; - } - - @Override - public int getBinsCount() { - return mBinCount; - } - - @Override - public int getBinForSample(float sample) { - if (sample < mMinValue) { - // goes to underflow - return 0; - } else if (sample >= mExclusiveMaxValue) { - // goes to overflow - return mBinCount - 1; - } - return (int) ((sample - mMinValue) / mBinSize + 1); - } - } - - /** Used by Histogram to map data sample to corresponding bin for scaled bins */ - public static final class ScaledRangeOptions implements BinOptions { - // store minimum value per bin - final long[] mBins; - - /** - * Creates options for scaled range bins - * - * @param binCount amount of histogram bins. 2 bin indexes will be calculated - * automatically to represent underflow & overflow bins - * @param minValue is included in the first bin, values less than minValue - * go to underflow bin - * @param firstBinWidth used to represent first bin width and as a reference to calculate - * width for consecutive bins - * @param scaleFactor used to calculate width for consecutive bins - * @hide - */ - public ScaledRangeOptions(@IntRange(from = 1) int binCount, int minValue, - @FloatRange(from = 1.f) float firstBinWidth, - @FloatRange(from = 1.f) float scaleFactor) { - if (binCount < 1) { - throw new IllegalArgumentException("Bin count should be positive number"); - } - - if (firstBinWidth < 1.f) { - throw new IllegalArgumentException( - "First bin width invalid (should be 1.f at minimum)"); - } - - if (scaleFactor < 1.f) { - throw new IllegalArgumentException( - "Scaled factor invalid (should be 1.f at minimum)"); - } - - // precalculating bins ranges (no need to create a bin for underflow reference value) - mBins = initBins(binCount + 1, minValue, firstBinWidth, scaleFactor); - } - - @Override - public int getBinsCount() { - return mBins.length + 1; - } - - @Override - public int getBinForSample(float sample) { - if (sample < mBins[0]) { - // goes to underflow - return 0; - } else if (sample >= mBins[mBins.length - 1]) { - // goes to overflow - return mBins.length; - } - - return lower_bound(mBins, (long) sample) + 1; - } - - // To find lower bound using binary search implementation of Arrays utility class - private static int lower_bound(long[] array, long sample) { - int index = Arrays.binarySearch(array, sample); - // If key is not present in the array - if (index < 0) { - // Index specify the position of the key when inserted in the sorted array - // so the element currently present at this position will be the lower bound - return Math.abs(index) - 2; - } - return index; - } - - private static long[] initBins(int count, int minValue, float firstBinWidth, - float scaleFactor) { - long[] bins = new long[count]; - bins[0] = minValue; - double lastWidth = firstBinWidth; - for (int i = 1; i < count; i++) { - // current bin minValue = previous bin width * scaleFactor - double currentBinMinValue = bins[i - 1] + lastWidth; - if (currentBinMinValue > Integer.MAX_VALUE) { - throw new IllegalArgumentException( - "Attempted to create a bucket larger than maxint"); - } - - bins[i] = (long) currentBinMinValue; - lastWidth *= scaleFactor; - } - return bins; - } - } -} diff --git a/core/java/com/android/internal/expresslog/OWNERS b/core/java/com/android/internal/expresslog/OWNERS deleted file mode 100644 index ee865b1e4ec8..000000000000 --- a/core/java/com/android/internal/expresslog/OWNERS +++ /dev/null @@ -1 +0,0 @@ -include /services/core/java/com/android/server/stats/OWNERS diff --git a/core/java/com/android/internal/expresslog/TEST_MAPPING b/core/java/com/android/internal/expresslog/TEST_MAPPING deleted file mode 100644 index c9b0cf80a1e6..000000000000 --- a/core/java/com/android/internal/expresslog/TEST_MAPPING +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presubmit": [ - { - "name": "ExpressLogTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - } - ] - } - ] -}
\ No newline at end of file diff --git a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java index e2c096c11fee..aa6b1c00d186 100644 --- a/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java +++ b/core/java/com/android/internal/os/KernelSingleProcessCpuThreadReader.java @@ -20,7 +20,7 @@ import android.annotation.Nullable; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.expresslog.Counter; +import com.android.modules.expresslog.Counter; import java.io.IOException; import java.util.Arrays; diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java index f6d80a572c75..8fe2b9cdf1e5 100644 --- a/core/java/com/android/internal/util/DumpUtils.java +++ b/core/java/com/android/internal/util/DumpUtils.java @@ -25,6 +25,7 @@ import android.os.Binder; import android.os.Handler; import android.text.TextUtils; import android.util.Slog; +import android.util.SparseArray; import java.io.PrintWriter; import java.io.StringWriter; @@ -312,5 +313,85 @@ public final class DumpUtils { || cn.flattenToString().toLowerCase().contains(filterString.toLowerCase()); }; } -} + /** + * Lambda used to dump a key (and its index) while iterating though a collection. + */ + public interface KeyDumper { + + /** Dumps the index and key.*/ + void dump(int index, int key); + } + + /** + * Lambda used to dump a value while iterating though a collection. + * + * @param <T> type of the value. + */ + public interface ValueDumper<T> { + + /** Dumps the value.*/ + void dump(T value); + } + + /** + * Dumps a sparse array. + */ + public static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array, + String name) { + dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valueDumper= */ null); + } + + /** + * Dumps the values of a sparse array. + */ + public static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix, + SparseArray<T> array, String name) { + dumpSparseArray(pw, prefix, array, name, (i, k) -> { + pw.printf("%s%s", prefix, prefix); + }, /* valueDumper= */ null); + } + + /** + * Dumps a sparse array, customizing each line. + */ + public static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array, + String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) { + int size = array.size(); + if (size == 0) { + pw.print(prefix); + pw.print("No "); + pw.print(name); + pw.println("s"); + return; + } + pw.print(prefix); + pw.print(size); + pw.print(' '); + pw.print(name); + pw.println("(s):"); + + String prefix2 = prefix + prefix; + for (int i = 0; i < size; i++) { + int key = array.keyAt(i); + T value = array.valueAt(i); + if (keyDumper != null) { + keyDumper.dump(i, key); + } else { + pw.print(prefix2); + pw.print(i); + pw.print(": "); + pw.print(key); + pw.print("->"); + } + if (value == null) { + pw.print("(null)"); + } else if (valueDumper != null) { + valueDumper.dump(value); + } else { + pw.print(value); + } + pw.println(); + } + } +} diff --git a/core/jni/Android.bp b/core/jni/Android.bp index fc26766d7090..6bec6bc236bd 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -225,7 +225,6 @@ cc_library_shared { "android_security_Scrypt.cpp", "com_android_internal_content_om_OverlayConfig.cpp", "com_android_internal_content_om_OverlayManagerImpl.cpp", - "com_android_internal_expresslog_Utils.cpp", "com_android_internal_net_NetworkUtilsInternal.cpp", "com_android_internal_os_ClassLoaderFactory.cpp", "com_android_internal_os_FuseAppLoop.cpp", @@ -262,6 +261,7 @@ cc_library_shared { "libstatssocket_lazy", "libskia", "libtextclassifier_hash_static", + "libexpresslog_jni", ], shared_libs: [ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index b550f28e0934..21bdf099f18d 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -200,7 +200,7 @@ extern int register_com_android_internal_content_F2fsUtils(JNIEnv* env); extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env); extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env); extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env); -extern int register_com_android_internal_expresslog_Utils(JNIEnv* env); +extern int register_com_android_modules_expresslog_Utils(JNIEnv* env); extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env); extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env); extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env); @@ -1586,7 +1586,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_incremental_IncrementalManager), REG_JNI(register_com_android_internal_content_om_OverlayConfig), REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl), - REG_JNI(register_com_android_internal_expresslog_Utils), + REG_JNI(register_com_android_modules_expresslog_Utils), REG_JNI(register_com_android_internal_net_NetworkUtilsInternal), REG_JNI(register_com_android_internal_os_ClassLoaderFactory), REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter), diff --git a/core/jni/OWNERS b/core/jni/OWNERS index bce533281186..4e4abec88040 100644 --- a/core/jni/OWNERS +++ b/core/jni/OWNERS @@ -104,6 +104,3 @@ per-file com_android_internal_os_*MultiStateCounter* = file:/BATTERY_STATS_OWNER # PM per-file com_android_internal_content_* = file:/PACKAGE_MANAGER_OWNERS - -# Stats/expresslog -per-file *expresslog* = file:/services/core/java/com/android/server/stats/OWNERS diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp index 8ba4eed8b34d..e1be0cd80bb6 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -2481,36 +2481,31 @@ static jint android_media_AudioSystem_getSurroundFormats(JNIEnv *env, jobject th if (jSurroundFormats == nullptr) { ALOGE("jSurroundFormats is NULL"); - return (jint)AUDIO_JAVA_BAD_VALUE; + return static_cast<jint>(AUDIO_JAVA_BAD_VALUE); } if (!env->IsInstanceOf(jSurroundFormats, gMapClass)) { ALOGE("getSurroundFormats not a map"); - return (jint)AUDIO_JAVA_BAD_VALUE; + return static_cast<jint>(AUDIO_JAVA_BAD_VALUE); } jint jStatus; unsigned int numSurroundFormats = 0; - audio_format_t *surroundFormats = nullptr; - bool *surroundFormatsEnabled = nullptr; - status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats, - surroundFormatsEnabled); + status_t status = AudioSystem::getSurroundFormats(&numSurroundFormats, nullptr, nullptr); if (status != NO_ERROR) { ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status); - jStatus = nativeToJavaStatus(status); - goto exit; + return nativeToJavaStatus(status); } if (numSurroundFormats == 0) { - jStatus = (jint)AUDIO_JAVA_SUCCESS; - goto exit; + return static_cast<jint>(AUDIO_JAVA_SUCCESS); } - surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t)); - surroundFormatsEnabled = (bool *)calloc(numSurroundFormats, sizeof(bool)); - status = AudioSystem::getSurroundFormats(&numSurroundFormats, surroundFormats, - surroundFormatsEnabled); + auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats); + auto surroundFormatsEnabled = std::make_unique<bool[]>(numSurroundFormats); + status = AudioSystem::getSurroundFormats(&numSurroundFormats, &surroundFormats[0], + &surroundFormatsEnabled[0]); jStatus = nativeToJavaStatus(status); if (status != NO_ERROR) { ALOGE_IF(status != NO_ERROR, "AudioSystem::getSurroundFormats error %d", status); - goto exit; + return jStatus; } for (size_t i = 0; i < numSurroundFormats; i++) { int audioFormat = audioFormatFromNative(surroundFormats[i]); @@ -2526,9 +2521,6 @@ static jint android_media_AudioSystem_getSurroundFormats(JNIEnv *env, jobject th env->DeleteLocalRef(enabled); } -exit: - free(surroundFormats); - free(surroundFormatsEnabled); return jStatus; } @@ -2538,31 +2530,28 @@ static jint android_media_AudioSystem_getReportedSurroundFormats(JNIEnv *env, jo if (jSurroundFormats == nullptr) { ALOGE("jSurroundFormats is NULL"); - return (jint)AUDIO_JAVA_BAD_VALUE; + return static_cast<jint>(AUDIO_JAVA_BAD_VALUE); } if (!env->IsInstanceOf(jSurroundFormats, gArrayListClass)) { ALOGE("jSurroundFormats not an arraylist"); - return (jint)AUDIO_JAVA_BAD_VALUE; + return static_cast<jint>(AUDIO_JAVA_BAD_VALUE); } jint jStatus; unsigned int numSurroundFormats = 0; - audio_format_t *surroundFormats = nullptr; - status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats); + status_t status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, nullptr); if (status != NO_ERROR) { ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status); - jStatus = nativeToJavaStatus(status); - goto exit; + return nativeToJavaStatus(status); } if (numSurroundFormats == 0) { - jStatus = (jint)AUDIO_JAVA_SUCCESS; - goto exit; + return static_cast<jint>(AUDIO_JAVA_SUCCESS); } - surroundFormats = (audio_format_t *)calloc(numSurroundFormats, sizeof(audio_format_t)); - status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, surroundFormats); + auto surroundFormats = std::make_unique<audio_format_t[]>(numSurroundFormats); + status = AudioSystem::getReportedSurroundFormats(&numSurroundFormats, &surroundFormats[0]); jStatus = nativeToJavaStatus(status); if (status != NO_ERROR) { ALOGE_IF(status != NO_ERROR, "AudioSystem::getReportedSurroundFormats error %d", status); - goto exit; + return jStatus; } for (size_t i = 0; i < numSurroundFormats; i++) { int audioFormat = audioFormatFromNative(surroundFormats[i]); @@ -2576,8 +2565,6 @@ static jint android_media_AudioSystem_getReportedSurroundFormats(JNIEnv *env, jo env->DeleteLocalRef(surroundFormat); } -exit: - free(surroundFormats); return jStatus; } diff --git a/core/jni/com_android_internal_expresslog_Utils.cpp b/core/jni/com_android_internal_expresslog_Utils.cpp deleted file mode 100644 index d33a7bda27f7..000000000000 --- a/core/jni/com_android_internal_expresslog_Utils.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include <nativehelper/JNIHelp.h> -#include <utils/hash/farmhash.h> - -#include "core_jni_helpers.h" - -// ---------------------------------------------------------------------------- -// JNI Glue -// ---------------------------------------------------------------------------- - -static jclass g_stringClass = nullptr; - -/** - * Class: com_android_internal_expresslog_Utils - * Method: hashString - * Signature: (Ljava/lang/String;)J - */ -static jlong hashString(JNIEnv* env, jclass /*class*/, jstring metricNameObj) { - ScopedUtfChars name(env, metricNameObj); - if (name.c_str() == nullptr) { - return 0; - } - - return static_cast<jlong>(farmhash::Fingerprint64(name.c_str(), name.size())); -} - -static const JNINativeMethod g_methods[] = { - {"hashString", "(Ljava/lang/String;)J", (void*)hashString}, -}; - -static const char* const kUtilsPathName = "com/android/internal/expresslog/Utils"; - -namespace android { - -int register_com_android_internal_expresslog_Utils(JNIEnv* env) { - jclass stringClass = FindClassOrDie(env, "java/lang/String"); - g_stringClass = MakeGlobalRefOrDie(env, stringClass); - - return RegisterMethodsOrDie(env, kUtilsPathName, g_methods, NELEM(g_methods)); -} - -} // namespace android diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto index ed612a05e6b5..025a57d0e334 100644 --- a/core/proto/android/server/activitymanagerservice.proto +++ b/core/proto/android/server/activitymanagerservice.proto @@ -1035,6 +1035,7 @@ message AppsExitInfoProto { optional int32 uid = 1; repeated .android.app.ApplicationExitInfoProto app_exit_info = 2; + repeated .android.app.ApplicationExitInfoProto app_recoverable_crash = 3; } repeated User users = 2; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 78d39236b392..05b38a562e29 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1746,37 +1746,6 @@ android:protectionLevel="dangerous" android:permissionFlags="hardRestricted" /> - <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors. - <p class="note"><strong>Note: </strong> This permission is for Wear OS only. - <p>Protection level: dangerous - @hide - --> - <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE" - android:permissionGroup="android.permission-group.UNDEFINED" - android:label="@string/permlab_bodySensorsWristTemperature" - android:description="@string/permdesc_bodySensorsWristTemperature" - android:backgroundPermission="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND" - android:protectionLevel="dangerous" /> - - <!-- @TestApi Allows an application to access wrist temperature data from the watch sensors. - If you're requesting this permission, you must also request - {@link #BODY_SENSORS_WRIST_TEMPERATURE}. Requesting this permission by itself doesn't - give you wrist temperature body sensors access. - <p class="note"><strong>Note: </strong> This permission is for Wear OS only. - <p>Protection level: dangerous - - <p> This is a hard restricted permission which cannot be held by an app until - the installer on record allowlists the permission. For more details see - {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}. - @hide - --> - <permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND" - android:permissionGroup="android.permission-group.UNDEFINED" - android:label="@string/permlab_bodySensors_wristTemperature_background" - android:description="@string/permdesc_bodySensors_wristTemperature_background" - android:protectionLevel="dangerous" - android:permissionFlags="hardRestricted" /> - <!-- Allows an app to use fingerprint hardware. <p>Protection level: normal @deprecated Applications should request {@link diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index e4d74b5373e0..9f10ae6066f5 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1373,6 +1373,9 @@ <!-- Number of notifications to keep in the notification service historical archive --> <integer name="config_notificationServiceArchiveSize">100</integer> + <!-- List of packages that will be able to use full screen intent in notifications by default --> + <string-array name="config_useFullScreenIntentPackages" translatable="false" /> + <!-- Allow the menu hard key to be disabled in LockScreen on some devices --> <bool name="config_disableMenuKeyInLockScreen">false</bool> diff --git a/core/res/res/values/public-final.xml b/core/res/res/values/public-final.xml index 85325fec7277..daa0f553f47a 100644 --- a/core/res/res/values/public-final.xml +++ b/core/res/res/values/public-final.xml @@ -3402,4 +3402,343 @@ <!-- @hide @SystemApi --> <public type="bool" name="config_enableQrCodeScannerOnLockScreen" id="0x01110008" /> + <!-- =============================================================== + Resources added in version U of the platform + + NOTE: After this version of the platform is forked, changes cannot be made to the root + branch's groups for that release. Only merge changes to the forked platform branch. + =============================================================== --> + <eat-comment/> + + <staging-public-group-final type="attr" first-id="0x01ce0000"> + <public name="handwritingBoundsOffsetLeft" /> + <public name="handwritingBoundsOffsetTop" /> + <public name="handwritingBoundsOffsetRight" /> + <public name="handwritingBoundsOffsetBottom" /> + <public name="accessibilityDataSensitive" /> + <public name="enableTextStylingShortcuts" /> + <public name="requiredDisplayCategory"/> + <public name="removed_maxConcurrentSessionsCount" /> + <public name="visualQueryDetectionService" /> + <public name="physicalKeyboardHintLanguageTag" /> + <public name="physicalKeyboardHintLayoutType" /> + <public name="allowSharedIsolatedProcess" /> + <public name="keyboardLocale" /> + <public name="keyboardLayoutType" /> + <public name="allowUpdateOwnership" /> + <public name="isCredential"/> + <public name="searchResultHighlightColor" /> + <public name="focusedSearchResultHighlightColor" /> + <public name="stylusHandwritingSettingsActivity" /> + <public name="windowNoMoveAnimation" /> + <public name="settingsSubtitle" /> + <public name="capability" /> + </staging-public-group-final> + + <public type="attr" name="handwritingBoundsOffsetLeft" id="0x01010673" /> + <public type="attr" name="handwritingBoundsOffsetTop" id="0x01010674" /> + <public type="attr" name="handwritingBoundsOffsetRight" id="0x01010675" /> + <public type="attr" name="handwritingBoundsOffsetBottom" id="0x01010676" /> + <public type="attr" name="accessibilityDataSensitive" id="0x01010677" /> + <public type="attr" name="enableTextStylingShortcuts" id="0x01010678" /> + <public type="attr" name="requiredDisplayCategory" id="0x01010679" /> + <public type="attr" name="visualQueryDetectionService" id="0x0101067a" /> + <public type="attr" name="physicalKeyboardHintLanguageTag" id="0x0101067b" /> + <public type="attr" name="physicalKeyboardHintLayoutType" id="0x0101067c" /> + <public type="attr" name="allowSharedIsolatedProcess" id="0x0101067d" /> + <public type="attr" name="keyboardLocale" id="0x0101067e" /> + <public type="attr" name="keyboardLayoutType" id="0x0101067f" /> + <public type="attr" name="allowUpdateOwnership" id="0x01010680" /> + <public type="attr" name="isCredential" id="0x01010681" /> + <public type="attr" name="searchResultHighlightColor" id="0x01010682" /> + <public type="attr" name="focusedSearchResultHighlightColor" id="0x01010683" /> + <public type="attr" name="stylusHandwritingSettingsActivity" id="0x01010684" /> + <public type="attr" name="windowNoMoveAnimation" id="0x01010685" /> + <public type="attr" name="settingsSubtitle" id="0x01010686" /> + <public type="attr" name="capability" id="0x01010687" /> + + <staging-public-group-final type="id" first-id="0x01cd0000"> + <public name="bold" /> + <public name="italic" /> + <public name="underline" /> + <public name="accessibilityActionScrollInDirection" /> + </staging-public-group-final> + + <public type="id" name="bold" id="0x0102005b" /> + <public type="id" name="italic" id="0x0102005c" /> + <public type="id" name="underline" id="0x0102005d" /> + <public type="id" name="accessibilityActionScrollInDirection" id="0x0102005e" /> + + <staging-public-group-final type="string" first-id="0x01cb0000"> + <!-- @hide @SystemApi --> + <public name="config_systemWearHealthService" /> + <!-- @hide @SystemApi --> + <public name="config_defaultNotes" /> + <!-- @hide @SystemApi --> + <public name="config_systemFinancedDeviceController" /> + <!-- @hide @SystemApi --> + <public name="config_systemCallStreaming" /> + </staging-public-group-final> + + <!-- @hide @SystemApi --> + <public type="string" name="config_systemWearHealthService" id="0x01040044" /> + <!-- @hide @SystemApi --> + <public type="string" name="config_defaultNotes" id="0x01040045" /> + <!-- @hide @SystemApi --> + <public type="string" name="config_systemFinancedDeviceController" id="0x01040046" /> + <!-- @hide @SystemApi --> + <public type="string" name="config_systemCallStreaming" id="0x01040047" /> + + <staging-public-group-final type="dimen" first-id="0x01ca0000"> + <!-- @hide @SystemApi --> + <public name="config_viewConfigurationHandwritingGestureLineMargin" /> + </staging-public-group-final> + + <!-- @hide @SystemApi --> + <public type="dimen" name="config_viewConfigurationHandwritingGestureLineMargin" id="0x0105000a" /> + + <staging-public-group-final type="color" first-id="0x01c90000"> + <public name="system_primary_container_light" /> + <public name="system_on_primary_container_light" /> + <public name="system_primary_light" /> + <public name="system_on_primary_light" /> + <public name="system_secondary_container_light" /> + <public name="system_on_secondary_container_light" /> + <public name="system_secondary_light" /> + <public name="system_on_secondary_light" /> + <public name="system_tertiary_container_light" /> + <public name="system_on_tertiary_container_light" /> + <public name="system_tertiary_light" /> + <public name="system_on_tertiary_light" /> + <public name="system_background_light" /> + <public name="system_on_background_light" /> + <public name="system_surface_light" /> + <public name="system_on_surface_light" /> + <public name="system_surface_container_low_light" /> + <public name="system_surface_container_lowest_light" /> + <public name="system_surface_container_light" /> + <public name="system_surface_container_high_light" /> + <public name="system_surface_container_highest_light" /> + <public name="system_surface_bright_light" /> + <public name="system_surface_dim_light" /> + <public name="system_surface_variant_light" /> + <public name="system_on_surface_variant_light" /> + <public name="system_outline_light" /> + <public name="system_error_light" /> + <public name="system_on_error_light" /> + <public name="system_error_container_light" /> + <public name="system_on_error_container_light" /> + <public name="removed_system_primary_fixed_light" /> + <public name="removed_system_primary_fixed_dim_light" /> + <public name="removed_system_on_primary_fixed_light" /> + <public name="removed_system_on_primary_fixed_variant_light" /> + <public name="removed_system_secondary_fixed_light" /> + <public name="removed_system_secondary_fixed_dim_light" /> + <public name="removed_system_on_secondary_fixed_light" /> + <public name="removed_system_on_secondary_fixed_variant_light" /> + <public name="removed_system_tertiary_fixed_light" /> + <public name="removed_system_tertiary_fixed_dim_light" /> + <public name="removed_system_on_tertiary_fixed_light" /> + <public name="removed_system_on_tertiary_fixed_variant_light" /> + <public name="system_control_activated_light" /> + <public name="system_control_normal_light" /> + <public name="system_control_highlight_light" /> + <public name="system_text_primary_inverse_light" /> + <public name="system_text_secondary_and_tertiary_inverse_light" /> + <public name="system_text_primary_inverse_disable_only_light" /> + <public name="system_text_secondary_and_tertiary_inverse_disabled_light" /> + <public name="system_text_hint_inverse_light" /> + <public name="system_palette_key_color_primary_light" /> + <public name="system_palette_key_color_secondary_light" /> + <public name="system_palette_key_color_tertiary_light" /> + <public name="system_palette_key_color_neutral_light" /> + <public name="system_palette_key_color_neutral_variant_light" /> + <public name="system_primary_container_dark"/> + <public name="system_on_primary_container_dark"/> + <public name="system_primary_dark"/> + <public name="system_on_primary_dark"/> + <public name="system_secondary_container_dark"/> + <public name="system_on_secondary_container_dark"/> + <public name="system_secondary_dark"/> + <public name="system_on_secondary_dark"/> + <public name="system_tertiary_container_dark"/> + <public name="system_on_tertiary_container_dark"/> + <public name="system_tertiary_dark"/> + <public name="system_on_tertiary_dark"/> + <public name="system_background_dark"/> + <public name="system_on_background_dark"/> + <public name="system_surface_dark"/> + <public name="system_on_surface_dark"/> + <public name="system_surface_container_low_dark"/> + <public name="system_surface_container_lowest_dark"/> + <public name="system_surface_container_dark"/> + <public name="system_surface_container_high_dark"/> + <public name="system_surface_container_highest_dark"/> + <public name="system_surface_bright_dark"/> + <public name="system_surface_dim_dark"/> + <public name="system_surface_variant_dark"/> + <public name="system_on_surface_variant_dark"/> + <public name="system_outline_dark"/> + <public name="system_error_dark"/> + <public name="system_on_error_dark"/> + <public name="system_error_container_dark"/> + <public name="system_on_error_container_dark"/> + <public name="removed_system_primary_fixed_dark"/> + <public name="removed_system_primary_fixed_dim_dark"/> + <public name="removed_system_on_primary_fixed_dark"/> + <public name="removed_system_on_primary_fixed_variant_dark"/> + <public name="removed_system_secondary_fixed_dark"/> + <public name="removed_system_secondary_fixed_dim_dark"/> + <public name="removed_system_on_secondary_fixed_dark"/> + <public name="removed_system_on_secondary_fixed_variant_dark"/> + <public name="removed_system_tertiary_fixed_dark"/> + <public name="removed_system_tertiary_fixed_dim_dark"/> + <public name="removed_system_on_tertiary_fixed_dark"/> + <public name="removed_system_on_tertiary_fixed_variant_dark"/> + <public name="system_control_activated_dark"/> + <public name="system_control_normal_dark"/> + <public name="system_control_highlight_dark"/> + <public name="system_text_primary_inverse_dark"/> + <public name="system_text_secondary_and_tertiary_inverse_dark"/> + <public name="system_text_primary_inverse_disable_only_dark"/> + <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/> + <public name="system_text_hint_inverse_dark"/> + <public name="system_palette_key_color_primary_dark"/> + <public name="system_palette_key_color_secondary_dark"/> + <public name="system_palette_key_color_tertiary_dark"/> + <public name="system_palette_key_color_neutral_dark"/> + <public name="system_palette_key_color_neutral_variant_dark"/> + <public name="system_primary_fixed" /> + <public name="system_primary_fixed_dim" /> + <public name="system_on_primary_fixed" /> + <public name="system_on_primary_fixed_variant" /> + <public name="system_secondary_fixed" /> + <public name="system_secondary_fixed_dim" /> + <public name="system_on_secondary_fixed" /> + <public name="system_on_secondary_fixed_variant" /> + <public name="system_tertiary_fixed" /> + <public name="system_tertiary_fixed_dim" /> + <public name="system_on_tertiary_fixed" /> + <public name="system_on_tertiary_fixed_variant" /> + <public name="system_outline_variant_light" /> + <public name="system_outline_variant_dark" /> + </staging-public-group-final> + + <public type="color" name="system_primary_container_light" id="0x0106005e" /> + <public type="color" name="system_on_primary_container_light" id="0x0106005f" /> + <public type="color" name="system_primary_light" id="0x01060060" /> + <public type="color" name="system_on_primary_light" id="0x01060061" /> + <public type="color" name="system_secondary_container_light" id="0x01060062" /> + <public type="color" name="system_on_secondary_container_light" id="0x01060063" /> + <public type="color" name="system_secondary_light" id="0x01060064" /> + <public type="color" name="system_on_secondary_light" id="0x01060065" /> + <public type="color" name="system_tertiary_container_light" id="0x01060066" /> + <public type="color" name="system_on_tertiary_container_light" id="0x01060067" /> + <public type="color" name="system_tertiary_light" id="0x01060068" /> + <public type="color" name="system_on_tertiary_light" id="0x01060069" /> + <public type="color" name="system_background_light" id="0x0106006a" /> + <public type="color" name="system_on_background_light" id="0x0106006b" /> + <public type="color" name="system_surface_light" id="0x0106006c" /> + <public type="color" name="system_on_surface_light" id="0x0106006d" /> + <public type="color" name="system_surface_container_low_light" id="0x0106006e" /> + <public type="color" name="system_surface_container_lowest_light" id="0x0106006f" /> + <public type="color" name="system_surface_container_light" id="0x01060070" /> + <public type="color" name="system_surface_container_high_light" id="0x01060071" /> + <public type="color" name="system_surface_container_highest_light" id="0x01060072" /> + <public type="color" name="system_surface_bright_light" id="0x01060073" /> + <public type="color" name="system_surface_dim_light" id="0x01060074" /> + <public type="color" name="system_surface_variant_light" id="0x01060075" /> + <public type="color" name="system_on_surface_variant_light" id="0x01060076" /> + <public type="color" name="system_outline_light" id="0x01060077" /> + <public type="color" name="system_error_light" id="0x01060078" /> + <public type="color" name="system_on_error_light" id="0x01060079" /> + <public type="color" name="system_error_container_light" id="0x0106007a" /> + <public type="color" name="system_on_error_container_light" id="0x0106007b" /> + <public type="color" name="system_control_activated_light" id="0x0106007c" /> + <public type="color" name="system_control_normal_light" id="0x0106007d" /> + <public type="color" name="system_control_highlight_light" id="0x0106007e" /> + <public type="color" name="system_text_primary_inverse_light" id="0x0106007f" /> + <public type="color" name="system_text_secondary_and_tertiary_inverse_light" id="0x01060080" /> + <public type="color" name="system_text_primary_inverse_disable_only_light" id="0x01060081" /> + <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_light" id="0x01060082" /> + <public type="color" name="system_text_hint_inverse_light" id="0x01060083" /> + <public type="color" name="system_palette_key_color_primary_light" id="0x01060084" /> + <public type="color" name="system_palette_key_color_secondary_light" id="0x01060085" /> + <public type="color" name="system_palette_key_color_tertiary_light" id="0x01060086" /> + <public type="color" name="system_palette_key_color_neutral_light" id="0x01060087" /> + <public type="color" name="system_palette_key_color_neutral_variant_light" id="0x01060088" /> + <public type="color" name="system_primary_container_dark" id="0x01060089" /> + <public type="color" name="system_on_primary_container_dark" id="0x0106008a" /> + <public type="color" name="system_primary_dark" id="0x0106008b" /> + <public type="color" name="system_on_primary_dark" id="0x0106008c" /> + <public type="color" name="system_secondary_container_dark" id="0x0106008d" /> + <public type="color" name="system_on_secondary_container_dark" id="0x0106008e" /> + <public type="color" name="system_secondary_dark" id="0x0106008f" /> + <public type="color" name="system_on_secondary_dark" id="0x01060090" /> + <public type="color" name="system_tertiary_container_dark" id="0x01060091" /> + <public type="color" name="system_on_tertiary_container_dark" id="0x01060092" /> + <public type="color" name="system_tertiary_dark" id="0x01060093" /> + <public type="color" name="system_on_tertiary_dark" id="0x01060094" /> + <public type="color" name="system_background_dark" id="0x01060095" /> + <public type="color" name="system_on_background_dark" id="0x01060096" /> + <public type="color" name="system_surface_dark" id="0x01060097" /> + <public type="color" name="system_on_surface_dark" id="0x01060098" /> + <public type="color" name="system_surface_container_low_dark" id="0x01060099" /> + <public type="color" name="system_surface_container_lowest_dark" id="0x0106009a" /> + <public type="color" name="system_surface_container_dark" id="0x0106009b" /> + <public type="color" name="system_surface_container_high_dark" id="0x0106009c" /> + <public type="color" name="system_surface_container_highest_dark" id="0x0106009d" /> + <public type="color" name="system_surface_bright_dark" id="0x0106009e" /> + <public type="color" name="system_surface_dim_dark" id="0x0106009f" /> + <public type="color" name="system_surface_variant_dark" id="0x010600a0" /> + <public type="color" name="system_on_surface_variant_dark" id="0x010600a1" /> + <public type="color" name="system_outline_dark" id="0x010600a2" /> + <public type="color" name="system_error_dark" id="0x010600a3" /> + <public type="color" name="system_on_error_dark" id="0x010600a4" /> + <public type="color" name="system_error_container_dark" id="0x010600a5" /> + <public type="color" name="system_on_error_container_dark" id="0x010600a6" /> + <public type="color" name="system_control_activated_dark" id="0x010600a7" /> + <public type="color" name="system_control_normal_dark" id="0x010600a8" /> + <public type="color" name="system_control_highlight_dark" id="0x010600a9" /> + <public type="color" name="system_text_primary_inverse_dark" id="0x010600aa" /> + <public type="color" name="system_text_secondary_and_tertiary_inverse_dark" id="0x010600ab" /> + <public type="color" name="system_text_primary_inverse_disable_only_dark" id="0x010600ac" /> + <public type="color" name="system_text_secondary_and_tertiary_inverse_disabled_dark" id="0x010600ad" /> + <public type="color" name="system_text_hint_inverse_dark" id="0x010600ae" /> + <public type="color" name="system_palette_key_color_primary_dark" id="0x010600af" /> + <public type="color" name="system_palette_key_color_secondary_dark" id="0x010600b0" /> + <public type="color" name="system_palette_key_color_tertiary_dark" id="0x010600b1" /> + <public type="color" name="system_palette_key_color_neutral_dark" id="0x010600b2" /> + <public type="color" name="system_palette_key_color_neutral_variant_dark" id="0x010600b3" /> + <public type="color" name="system_primary_fixed" id="0x010600b4" /> + <public type="color" name="system_primary_fixed_dim" id="0x010600b5" /> + <public type="color" name="system_on_primary_fixed" id="0x010600b6" /> + <public type="color" name="system_on_primary_fixed_variant" id="0x010600b7" /> + <public type="color" name="system_secondary_fixed" id="0x010600b8" /> + <public type="color" name="system_secondary_fixed_dim" id="0x010600b9" /> + <public type="color" name="system_on_secondary_fixed" id="0x010600ba" /> + <public type="color" name="system_on_secondary_fixed_variant" id="0x010600bb" /> + <public type="color" name="system_tertiary_fixed" id="0x010600bc" /> + <public type="color" name="system_tertiary_fixed_dim" id="0x010600bd" /> + <public type="color" name="system_on_tertiary_fixed" id="0x010600be" /> + <public type="color" name="system_on_tertiary_fixed_variant" id="0x010600bf" /> + <public type="color" name="system_outline_variant_light" id="0x010600c0" /> + <public type="color" name="system_outline_variant_dark" id="0x010600c1" /> + + <staging-public-group-final type="bool" first-id="0x01be0000"> + <!-- @hide @SystemApi --> + <public name="config_safetyProtectionEnabled" /> + <!-- @hide @SystemApi --> + <public name="config_enableDefaultNotes" /> + <!-- @hide @SystemApi --> + <public name="config_enableDefaultNotesForWorkProfile" /> + </staging-public-group-final> + + <!-- @hide @SystemApi --> + <public type="bool" name="config_safetyProtectionEnabled" id="0x01110009" /> + <!-- @hide @SystemApi --> + <public type="bool" name="config_enableDefaultNotes" id="0x0111000a" /> + <!-- @hide @SystemApi --> + <public type="bool" name="config_enableDefaultNotesForWorkProfile" id="0x0111000b" /> + </resources> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 9cbf3b679851..49a19407e59f 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -102,231 +102,65 @@ <resources> <!-- =============================================================== - Resources added in version U of the platform + Resources added in version NEXT of the platform NOTE: After this version of the platform is forked, changes cannot be made to the root branch's groups for that release. Only merge changes to the forked platform branch. =============================================================== --> <eat-comment/> - <staging-public-group type="attr" first-id="0x01ce0000"> - <public name="handwritingBoundsOffsetLeft" /> - <public name="handwritingBoundsOffsetTop" /> - <public name="handwritingBoundsOffsetRight" /> - <public name="handwritingBoundsOffsetBottom" /> - <public name="accessibilityDataSensitive" /> - <public name="enableTextStylingShortcuts" /> - <public name="requiredDisplayCategory"/> - <public name="removed_maxConcurrentSessionsCount" /> - <public name="visualQueryDetectionService" /> - <public name="physicalKeyboardHintLanguageTag" /> - <public name="physicalKeyboardHintLayoutType" /> - <public name="allowSharedIsolatedProcess" /> - <public name="keyboardLocale" /> - <public name="keyboardLayoutType" /> - <public name="allowUpdateOwnership" /> - <public name="isCredential"/> - <public name="searchResultHighlightColor" /> - <public name="focusedSearchResultHighlightColor" /> - <public name="stylusHandwritingSettingsActivity" /> - <public name="windowNoMoveAnimation" /> - <public name="settingsSubtitle" /> - <public name="capability" /> + <staging-public-group type="attr" first-id="0x01bd0000"> </staging-public-group> - <staging-public-group type="id" first-id="0x01cd0000"> - <public name="bold" /> - <public name="italic" /> - <public name="underline" /> - <public name="accessibilityActionScrollInDirection" /> + <staging-public-group type="id" first-id="0x01bc0000"> </staging-public-group> - <staging-public-group type="style" first-id="0x01cc0000"> + <staging-public-group type="style" first-id="0x01bb0000"> </staging-public-group> - <staging-public-group type="string" first-id="0x01cb0000"> - <!-- @hide @SystemApi --> - <public name="config_systemWearHealthService" /> - <!-- @hide @SystemApi --> - <public name="config_defaultNotes" /> - <!-- @hide @SystemApi --> - <public name="config_systemFinancedDeviceController" /> - <!-- @hide @SystemApi --> - <public name="config_systemCallStreaming" /> + <staging-public-group type="string" first-id="0x01ba0000"> </staging-public-group> - <staging-public-group type="dimen" first-id="0x01ca0000"> - <!-- @hide @SystemApi --> - <public name="config_viewConfigurationHandwritingGestureLineMargin" /> + <staging-public-group type="dimen" first-id="0x01b90000"> </staging-public-group> - <staging-public-group type="color" first-id="0x01c90000"> - <public name="system_primary_container_light" /> - <public name="system_on_primary_container_light" /> - <public name="system_primary_light" /> - <public name="system_on_primary_light" /> - <public name="system_secondary_container_light" /> - <public name="system_on_secondary_container_light" /> - <public name="system_secondary_light" /> - <public name="system_on_secondary_light" /> - <public name="system_tertiary_container_light" /> - <public name="system_on_tertiary_container_light" /> - <public name="system_tertiary_light" /> - <public name="system_on_tertiary_light" /> - <public name="system_background_light" /> - <public name="system_on_background_light" /> - <public name="system_surface_light" /> - <public name="system_on_surface_light" /> - <public name="system_surface_container_low_light" /> - <public name="system_surface_container_lowest_light" /> - <public name="system_surface_container_light" /> - <public name="system_surface_container_high_light" /> - <public name="system_surface_container_highest_light" /> - <public name="system_surface_bright_light" /> - <public name="system_surface_dim_light" /> - <public name="system_surface_variant_light" /> - <public name="system_on_surface_variant_light" /> - <public name="system_outline_light" /> - <public name="system_error_light" /> - <public name="system_on_error_light" /> - <public name="system_error_container_light" /> - <public name="system_on_error_container_light" /> - <public name="removed_system_primary_fixed_light" /> - <public name="removed_system_primary_fixed_dim_light" /> - <public name="removed_system_on_primary_fixed_light" /> - <public name="removed_system_on_primary_fixed_variant_light" /> - <public name="removed_system_secondary_fixed_light" /> - <public name="removed_system_secondary_fixed_dim_light" /> - <public name="removed_system_on_secondary_fixed_light" /> - <public name="removed_system_on_secondary_fixed_variant_light" /> - <public name="removed_system_tertiary_fixed_light" /> - <public name="removed_system_tertiary_fixed_dim_light" /> - <public name="removed_system_on_tertiary_fixed_light" /> - <public name="removed_system_on_tertiary_fixed_variant_light" /> - <public name="system_control_activated_light" /> - <public name="system_control_normal_light" /> - <public name="system_control_highlight_light" /> - <public name="system_text_primary_inverse_light" /> - <public name="system_text_secondary_and_tertiary_inverse_light" /> - <public name="system_text_primary_inverse_disable_only_light" /> - <public name="system_text_secondary_and_tertiary_inverse_disabled_light" /> - <public name="system_text_hint_inverse_light" /> - <public name="system_palette_key_color_primary_light" /> - <public name="system_palette_key_color_secondary_light" /> - <public name="system_palette_key_color_tertiary_light" /> - <public name="system_palette_key_color_neutral_light" /> - <public name="system_palette_key_color_neutral_variant_light" /> - <public name="system_primary_container_dark"/> - <public name="system_on_primary_container_dark"/> - <public name="system_primary_dark"/> - <public name="system_on_primary_dark"/> - <public name="system_secondary_container_dark"/> - <public name="system_on_secondary_container_dark"/> - <public name="system_secondary_dark"/> - <public name="system_on_secondary_dark"/> - <public name="system_tertiary_container_dark"/> - <public name="system_on_tertiary_container_dark"/> - <public name="system_tertiary_dark"/> - <public name="system_on_tertiary_dark"/> - <public name="system_background_dark"/> - <public name="system_on_background_dark"/> - <public name="system_surface_dark"/> - <public name="system_on_surface_dark"/> - <public name="system_surface_container_low_dark"/> - <public name="system_surface_container_lowest_dark"/> - <public name="system_surface_container_dark"/> - <public name="system_surface_container_high_dark"/> - <public name="system_surface_container_highest_dark"/> - <public name="system_surface_bright_dark"/> - <public name="system_surface_dim_dark"/> - <public name="system_surface_variant_dark"/> - <public name="system_on_surface_variant_dark"/> - <public name="system_outline_dark"/> - <public name="system_error_dark"/> - <public name="system_on_error_dark"/> - <public name="system_error_container_dark"/> - <public name="system_on_error_container_dark"/> - <public name="removed_system_primary_fixed_dark"/> - <public name="removed_system_primary_fixed_dim_dark"/> - <public name="removed_system_on_primary_fixed_dark"/> - <public name="removed_system_on_primary_fixed_variant_dark"/> - <public name="removed_system_secondary_fixed_dark"/> - <public name="removed_system_secondary_fixed_dim_dark"/> - <public name="removed_system_on_secondary_fixed_dark"/> - <public name="removed_system_on_secondary_fixed_variant_dark"/> - <public name="removed_system_tertiary_fixed_dark"/> - <public name="removed_system_tertiary_fixed_dim_dark"/> - <public name="removed_system_on_tertiary_fixed_dark"/> - <public name="removed_system_on_tertiary_fixed_variant_dark"/> - <public name="system_control_activated_dark"/> - <public name="system_control_normal_dark"/> - <public name="system_control_highlight_dark"/> - <public name="system_text_primary_inverse_dark"/> - <public name="system_text_secondary_and_tertiary_inverse_dark"/> - <public name="system_text_primary_inverse_disable_only_dark"/> - <public name="system_text_secondary_and_tertiary_inverse_disabled_dark"/> - <public name="system_text_hint_inverse_dark"/> - <public name="system_palette_key_color_primary_dark"/> - <public name="system_palette_key_color_secondary_dark"/> - <public name="system_palette_key_color_tertiary_dark"/> - <public name="system_palette_key_color_neutral_dark"/> - <public name="system_palette_key_color_neutral_variant_dark"/> - <public name="system_primary_fixed" /> - <public name="system_primary_fixed_dim" /> - <public name="system_on_primary_fixed" /> - <public name="system_on_primary_fixed_variant" /> - <public name="system_secondary_fixed" /> - <public name="system_secondary_fixed_dim" /> - <public name="system_on_secondary_fixed" /> - <public name="system_on_secondary_fixed_variant" /> - <public name="system_tertiary_fixed" /> - <public name="system_tertiary_fixed_dim" /> - <public name="system_on_tertiary_fixed" /> - <public name="system_on_tertiary_fixed_variant" /> - <public name="system_outline_variant_light" /> - <public name="system_outline_variant_dark" /> + <staging-public-group type="color" first-id="0x01b80000"> </staging-public-group> - <staging-public-group type="array" first-id="0x01c80000"> + <staging-public-group type="array" first-id="0x01b70000"> </staging-public-group> - <staging-public-group type="drawable" first-id="0x01c70000"> + <staging-public-group type="drawable" first-id="0x01b60000"> </staging-public-group> - <staging-public-group type="layout" first-id="0x01c60000"> + <staging-public-group type="layout" first-id="0x01b50000"> </staging-public-group> - <staging-public-group type="anim" first-id="0x01c50000"> + <staging-public-group type="anim" first-id="0x01b40000"> </staging-public-group> - <staging-public-group type="animator" first-id="0x01c40000"> + <staging-public-group type="animator" first-id="0x01b30000"> </staging-public-group> - <staging-public-group type="interpolator" first-id="0x01c30000"> + <staging-public-group type="interpolator" first-id="0x01b20000"> </staging-public-group> - <staging-public-group type="mipmap" first-id="0x01c20000"> + <staging-public-group type="mipmap" first-id="0x01b10000"> </staging-public-group> - <staging-public-group type="integer" first-id="0x01c10000"> + <staging-public-group type="integer" first-id="0x01b00000"> </staging-public-group> - <staging-public-group type="transition" first-id="0x01c00000"> + <staging-public-group type="transition" first-id="0x01af0000"> </staging-public-group> - <staging-public-group type="raw" first-id="0x01bf0000"> + <staging-public-group type="raw" first-id="0x01ae0000"> </staging-public-group> - <staging-public-group type="bool" first-id="0x01be0000"> - <!-- @hide @SystemApi --> - <public name="config_safetyProtectionEnabled" /> - <!-- @hide @SystemApi --> - <public name="config_enableDefaultNotes" /> - <!-- @hide @SystemApi --> - <public name="config_enableDefaultNotesForWorkProfile" /> + <staging-public-group type="bool" first-id="0x01ad0000"> </staging-public-group> - <staging-public-group type="fraction" first-id="0x01bd0000"> + <staging-public-group type="fraction" first-id="0x01ac0000"> </staging-public-group> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 82314072fbac..947dc2de9841 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1347,16 +1347,6 @@ <!-- Description of the background body sensors permission, listed so the user can decide whether to allow the application to access data from body sensors in the background. [CHAR LIMIT=NONE] --> <string name="permdesc_bodySensors_background" product="default">Allows the app to access body sensor data, such as heart rate, temperature, and blood oxygen percentage, while the app is in the background.</string> - <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] --> - <string name="permlab_bodySensorsWristTemperature">Access body sensor wrist temperature data while the app is in use.</string> - <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] --> - <string name="permdesc_bodySensorsWristTemperature" product="default">Allows the app to access body sensor wrist temperature data, while the app is in use.</string> - - <!-- Title of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access body sensor wrist temperature data. [CHAR LIMIT=NONE] --> - <string name="permlab_bodySensors_wristTemperature_background">Access body sensor wrist temperature data while the app is in the background.</string> - <!-- Description of the body sensors wrist temperature permission, listed so the user can decide whether to allow the application to access data from body sensors. [CHAR LIMIT=NONE] --> - <string name="permdesc_bodySensors_wristTemperature_background" product="default">Allows the app to access body sensor wrist temperature data, while the app is in the background.</string> - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_readCalendar">Read calendar events and details</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c6c1c8f120d7..a823d1fd8ff4 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2017,6 +2017,7 @@ <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" /> <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" /> <java-symbol type="integer" name="config_notificationServiceArchiveSize" /> + <java-symbol type="array" name="config_useFullScreenIntentPackages" /> <java-symbol type="integer" name="config_previousVibrationsDumpLimit" /> <java-symbol type="integer" name="config_defaultVibrationAmplitude" /> <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" /> 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/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/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java index 980211fe4cc8..c6bb07b17fd4 100644 --- a/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java +++ b/core/tests/coretests/src/android/content/res/FontScaleConverterActivityTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import android.app.Activity; import android.compat.testing.PlatformCompatChangeRule; import android.os.Bundle; +import android.platform.test.annotations.IwTest; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import android.util.PollingCheck; @@ -84,6 +85,7 @@ public class FontScaleConverterActivityTest { } } + @IwTest(focusArea = "accessibility") @Test public void testFontsScaleNonLinearly() { final ActivityScenario<TestActivity> scenario = rule.getScenario(); @@ -114,6 +116,7 @@ public class FontScaleConverterActivityTest { ))); } + @IwTest(focusArea = "accessibility") @Test public void testOnConfigurationChanged_doesNotCrash() { final ActivityScenario<TestActivity> scenario = rule.getScenario(); @@ -127,6 +130,7 @@ public class FontScaleConverterActivityTest { }); } + @IwTest(focusArea = "accessibility") @Test public void testUpdateConfiguration_doesNotCrash() { final ActivityScenario<TestActivity> scenario = rule.getScenario(); diff --git a/core/tests/coretests/src/android/content/res/TEST_MAPPING b/core/tests/coretests/src/android/content/res/TEST_MAPPING index 4ea6e40a7225..ab14950891c3 100644 --- a/core/tests/coretests/src/android/content/res/TEST_MAPPING +++ b/core/tests/coretests/src/android/content/res/TEST_MAPPING @@ -39,5 +39,18 @@ } ] } + ], + "ironwood-postsubmit": [ + { + "name": "FrameworksCoreTests", + "options":[ + { + "include-annotation": "android.platform.test.annotations.IwTest" + }, + { + "exclude-annotation": "org.junit.Ignore" + } + ] + } ] } diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java new file mode 100644 index 000000000000..66f3bca72aeb --- /dev/null +++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java @@ -0,0 +1,102 @@ +/* + * 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.hardware.biometrics; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.RemoteException; +import android.os.test.TestLooper; +import android.platform.test.annotations.Presubmit; + +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.junit.MockitoJUnit; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.MockitoRule; + +import java.util.concurrent.Executor; + + +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class BiometricPromptTest { + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private Context mContext; + @Mock + private IAuthService mService; + private BiometricPrompt mBiometricPrompt; + + private CancellationSignal mCancellationSignal; + + private final TestLooper mLooper = new TestLooper(); + private final Handler mHandler = new Handler(mLooper.getLooper()); + private final Executor mExecutor = mHandler::post; + + @Before + public void setUp() throws RemoteException { + mBiometricPrompt = new BiometricPrompt.Builder(mContext) + .setUseDefaultSubtitle() + .setUseDefaultTitle() + .setAllowedAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG + | BiometricManager.Authenticators.DEVICE_CREDENTIAL) + .setService(mService) + .build(); + + mCancellationSignal = new CancellationSignal(); + when(mService.authenticate(any(), anyLong(), anyInt(), any(), anyString(), any())) + .thenReturn(0L); + when(mContext.getPackageName()).thenReturn("BiometricPromptTest"); + } + + @Test + public void testCancellationAfterAuthenticationFailed() throws RemoteException { + ArgumentCaptor<IBiometricServiceReceiver> biometricServiceReceiverCaptor = + ArgumentCaptor.forClass(IBiometricServiceReceiver.class); + BiometricPrompt.AuthenticationCallback callback = + new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationError(int errorCode, CharSequence errString) { + super.onAuthenticationError(errorCode, errString); + }}; + mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback); + mLooper.dispatchAll(); + + verify(mService).authenticate(any(), anyLong(), anyInt(), + biometricServiceReceiverCaptor.capture(), anyString(), any()); + + biometricServiceReceiverCaptor.getValue().onAuthenticationFailed(); + mLooper.dispatchAll(); + mCancellationSignal.cancel(); + + verify(mService).cancelAuthentication(any(), anyString(), anyLong()); + } +} diff --git a/core/tests/coretests/src/android/hardware/biometrics/OWNERS b/core/tests/coretests/src/android/hardware/biometrics/OWNERS new file mode 100644 index 000000000000..6a2192a2c7fb --- /dev/null +++ b/core/tests/coretests/src/android/hardware/biometrics/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/biometrics/OWNERS diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java index 3b6e8eaafc22..cde100cc20aa 100644 --- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java +++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java @@ -70,7 +70,13 @@ public class WindowOnBackInvokedDispatcherTest { private ApplicationInfo mApplicationInfo; private final BackMotionEvent mBackEvent = new BackMotionEvent( - 0, 0, 0, BackEvent.EDGE_LEFT, null); + /* touchX = */ 0, + /* touchY = */ 0, + /* progress = */ 0, + /* velocityX = */ 0, + /* velocityY = */ 0, + /* swipeEdge = */ BackEvent.EDGE_LEFT, + /* departingAnimationTarget = */ null); @Before public void setUp() throws Exception { diff --git a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java index 4716312c59a8..36c2a62ae6ed 100644 --- a/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java +++ b/core/tests/coretests/src/com/android/internal/util/DumpUtilsTest.java @@ -23,16 +23,25 @@ import static com.android.internal.util.DumpUtils.isPlatformCriticalPackage; import static com.android.internal.util.DumpUtils.isPlatformNonCriticalPackage; import static com.android.internal.util.DumpUtils.isPlatformPackage; +import static com.google.common.truth.Truth.assertWithMessage; + import android.content.ComponentName; +import android.util.SparseArray; import junit.framework.TestCase; +import java.io.PrintWriter; +import java.io.StringWriter; + /** * Run with: atest FrameworksCoreTests:DumpUtilsTest */ public class DumpUtilsTest extends TestCase { + private final StringWriter mStringWriter = new StringWriter(); + private final PrintWriter mPrintWriter = new PrintWriter(mStringWriter); + private static ComponentName cn(String componentName) { if (componentName == null) { return null; @@ -168,4 +177,144 @@ public class DumpUtilsTest extends TestCase { Integer.toHexString(System.identityHashCode(component))).test( wcn("com.google/.abc"))); } + + public void testDumpSparseArray_empty() { + SparseArray<String> array = new SparseArray<>(); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ "...", array, "whatever"); + + String output = flushPrintWriter(); + + assertWithMessage("empty array dump").that(output).isEqualTo("...No whatevers\n"); + } + + public void testDumpSparseArray_oneElement() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, "uno"); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number"); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".1 number(s):\n" + + "..0: 1->uno\n"); + } + + public void testDumpSparseArray_oneNullElement() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, null); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "NULL"); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".1 NULL(s):\n" + + "..0: 1->(null)\n"); + } + + public void testDumpSparseArray_multipleElements() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, "uno"); + array.put(2, "duo"); + array.put(42, null); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number"); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".3 number(s):\n" + + "..0: 1->uno\n" + + "..1: 2->duo\n" + + "..2: 42->(null)\n"); + } + + public void testDumpSparseArray_keyDumperOnly() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, "uno"); + array.put(2, "duo"); + array.put(42, null); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number", + (i, k) -> { + mPrintWriter.printf("_%d=%d_", i, k); + }, /* valueDumper= */ null); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".3 number(s):\n" + + "_0=1_uno\n" + + "_1=2_duo\n" + + "_2=42_(null)\n"); + } + + public void testDumpSparseArray_valueDumperOnly() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, "uno"); + array.put(2, "duo"); + array.put(42, null); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number", + /* keyDumper= */ null, + s -> { + mPrintWriter.print(s.toUpperCase()); + }); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".3 number(s):\n" + + "..0: 1->UNO\n" + + "..1: 2->DUO\n" + + "..2: 42->(null)\n"); + } + + public void testDumpSparseArray_keyAndValueDumpers() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, "uno"); + array.put(2, "duo"); + array.put(42, null); + + DumpUtils.dumpSparseArray(mPrintWriter, /* prefix= */ ".", array, "number", + (i, k) -> { + mPrintWriter.printf("_%d=%d_", i, k); + }, + s -> { + mPrintWriter.print(s.toUpperCase()); + }); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".3 number(s):\n" + + "_0=1_UNO\n" + + "_1=2_DUO\n" + + "_2=42_(null)\n"); + } + + public void testDumpSparseArrayValues() { + SparseArray<String> array = new SparseArray<>(); + array.put(1, "uno"); + array.put(2, "duo"); + array.put(42, null); + + DumpUtils.dumpSparseArrayValues(mPrintWriter, /* prefix= */ ".", array, "number"); + + String output = flushPrintWriter(); + + assertWithMessage("dump of %s", array).that(output).isEqualTo("" + + ".3 numbers:\n" + + "..uno\n" + + "..duo\n" + + "..(null)\n"); + } + + private String flushPrintWriter() { + mPrintWriter.flush(); + + return mStringWriter.toString(); + } } diff --git a/core/tests/expresslog/AndroidManifest.xml b/core/tests/expresslog/AndroidManifest.xml deleted file mode 100644 index 94a39e06c974..000000000000 --- a/core/tests/expresslog/AndroidManifest.xml +++ /dev/null @@ -1,29 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2023 The Android Open Source Project - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. ---> - -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - android:installLocation="internalOnly" - package="com.android.internal.expresslog" > - - <application > - <uses-library android:name="android.test.runner" /> - </application> - - <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" - android:targetPackage="com.android.internal.expresslog" - android:label="Telemetry Express Logging Helper Tests" /> - -</manifest> diff --git a/core/tests/expresslog/OWNERS b/core/tests/expresslog/OWNERS deleted file mode 100644 index 3dc958b07f9c..000000000000 --- a/core/tests/expresslog/OWNERS +++ /dev/null @@ -1,3 +0,0 @@ -# Bug component: 719316 -# Stats/expresslog -file:/services/core/java/com/android/server/stats/OWNERS diff --git a/core/tests/expresslog/TEST_MAPPING b/core/tests/expresslog/TEST_MAPPING deleted file mode 100644 index c9b0cf80a1e6..000000000000 --- a/core/tests/expresslog/TEST_MAPPING +++ /dev/null @@ -1,12 +0,0 @@ -{ - "presubmit": [ - { - "name": "ExpressLogTests", - "options": [ - { - "exclude-annotation": "org.junit.Ignore" - } - ] - } - ] -}
\ No newline at end of file diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java deleted file mode 100644 index ee62d7528818..000000000000 --- a/core/tests/expresslog/src/com/android/internal/expresslog/ScaledRangeOptionsTest.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.expresslog; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import androidx.test.filters.SmallTest; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -@SmallTest -public class ScaledRangeOptionsTest { - private static final String TAG = ScaledRangeOptionsTest.class.getSimpleName(); - - @Test - public void testGetBinsCount() { - Histogram.ScaledRangeOptions options1 = new Histogram.ScaledRangeOptions(1, 100, 100, 2); - assertEquals(3, options1.getBinsCount()); - - Histogram.ScaledRangeOptions options10 = new Histogram.ScaledRangeOptions(10, 100, 100, 2); - assertEquals(12, options10.getBinsCount()); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructZeroBinsCount() { - new Histogram.ScaledRangeOptions(0, 100, 100, 2); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructNegativeBinsCount() { - new Histogram.ScaledRangeOptions(-1, 100, 100, 2); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructNegativeFirstBinWidth() { - new Histogram.ScaledRangeOptions(10, 100, -100, 2); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructTooSmallFirstBinWidth() { - new Histogram.ScaledRangeOptions(10, 100, 0.5f, 2); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructNegativeScaleFactor() { - new Histogram.ScaledRangeOptions(10, 100, 100, -2); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructTooSmallScaleFactor() { - new Histogram.ScaledRangeOptions(10, 100, 100, 0.5f); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructTooBigScaleFactor() { - new Histogram.ScaledRangeOptions(10, 100, 100, 500.f); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructTooBigBinRange() { - new Histogram.ScaledRangeOptions(100, 100, 100, 10.f); - } - - @Test - public void testBinIndexForRangeEqual1() { - Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 1, 1); - assertEquals(12, options.getBinsCount()); - - assertEquals(11, options.getBinForSample(11)); - - for (int i = 0, bins = options.getBinsCount(); i < bins; i++) { - assertEquals(i, options.getBinForSample(i)); - } - } - - @Test - public void testBinIndexForRangeEqual2() { - // this should produce bin otpions similar to linear histogram with bin width 2 - Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 2, 1); - assertEquals(12, options.getBinsCount()); - - for (int i = 0, bins = options.getBinsCount(); i < bins; i++) { - assertEquals(i, options.getBinForSample(i * 2)); - assertEquals(i, options.getBinForSample(i * 2 - 1)); - } - } - - @Test - public void testBinIndexForRangeEqual5() { - Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(2, 0, 5, 1); - assertEquals(4, options.getBinsCount()); - for (int i = 0; i < 2; i++) { - for (int sample = 0; sample < 5; sample++) { - assertEquals(i + 1, options.getBinForSample(i * 5 + sample)); - } - } - } - - @Test - public void testBinIndexForRangeEqual10() { - Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions(10, 1, 10, 1); - assertEquals(0, options.getBinForSample(0)); - assertEquals(options.getBinsCount() - 2, options.getBinForSample(100)); - assertEquals(options.getBinsCount() - 1, options.getBinForSample(101)); - - final float binSize = (101 - 1) / 10f; - for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) { - assertEquals(i, options.getBinForSample(i * binSize)); - } - } - - @Test - public void testBinIndexForScaleFactor2() { - final int binsCount = 10; - final int minValue = 10; - final int firstBinWidth = 5; - final int scaledFactor = 2; - - Histogram.ScaledRangeOptions options = new Histogram.ScaledRangeOptions( - binsCount, minValue, firstBinWidth, scaledFactor); - assertEquals(binsCount + 2, options.getBinsCount()); - long[] binCounts = new long[10]; - - // precalculate max valid value - start value for the overflow bin - int lastBinStartValue = minValue; //firstBinMin value - int lastBinWidth = firstBinWidth; - for (int binIdx = 2; binIdx <= binsCount + 1; binIdx++) { - lastBinStartValue = lastBinStartValue + lastBinWidth; - lastBinWidth *= scaledFactor; - } - - // underflow bin - for (int i = 1; i < minValue; i++) { - assertEquals(0, options.getBinForSample(i)); - } - - for (int i = 10; i < lastBinStartValue; i++) { - assertTrue(options.getBinForSample(i) > 0); - assertTrue(options.getBinForSample(i) <= binsCount); - binCounts[options.getBinForSample(i) - 1]++; - } - - // overflow bin - assertEquals(binsCount + 1, options.getBinForSample(lastBinStartValue)); - - for (int i = 1; i < binsCount; i++) { - assertEquals(binCounts[i], binCounts[i - 1] * 2L); - } - } -} diff --git a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java b/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java deleted file mode 100644 index 037dbb32c2f8..000000000000 --- a/core/tests/expresslog/src/com/android/internal/expresslog/UniformOptionsTest.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.internal.expresslog; - -import androidx.test.filters.SmallTest; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -@SmallTest -public class UniformOptionsTest { - private static final String TAG = UniformOptionsTest.class.getSimpleName(); - - @Test - public void testGetBinsCount() { - Histogram.UniformOptions options1 = new Histogram.UniformOptions(1, 100, 1000); - assertEquals(3, options1.getBinsCount()); - - Histogram.UniformOptions options10 = new Histogram.UniformOptions(10, 100, 1000); - assertEquals(12, options10.getBinsCount()); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructZeroBinsCount() { - new Histogram.UniformOptions(0, 100, 1000); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructNegativeBinsCount() { - new Histogram.UniformOptions(-1, 100, 1000); - } - - @Test(expected = IllegalArgumentException.class) - public void testConstructMaxValueLessThanMinValue() { - new Histogram.UniformOptions(10, 1000, 100); - } - - @Test - public void testBinIndexForRangeEqual1() { - Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 11); - for (int i = 0, bins = options.getBinsCount(); i < bins; i++) { - assertEquals(i, options.getBinForSample(i)); - } - } - - @Test - public void testBinIndexForRangeEqual2() { - Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 21); - for (int i = 0, bins = options.getBinsCount(); i < bins; i++) { - assertEquals(i, options.getBinForSample(i * 2)); - assertEquals(i, options.getBinForSample(i * 2 - 1)); - } - } - - @Test - public void testBinIndexForRangeEqual5() { - Histogram.UniformOptions options = new Histogram.UniformOptions(2, 0, 10); - assertEquals(4, options.getBinsCount()); - for (int i = 0; i < 2; i++) { - for (int sample = 0; sample < 5; sample++) { - assertEquals(i + 1, options.getBinForSample(i * 5 + sample)); - } - } - } - - @Test - public void testBinIndexForRangeEqual10() { - Histogram.UniformOptions options = new Histogram.UniformOptions(10, 1, 101); - assertEquals(0, options.getBinForSample(0)); - assertEquals(options.getBinsCount() - 2, options.getBinForSample(100)); - assertEquals(options.getBinsCount() - 1, options.getBinForSample(101)); - - final float binSize = (101 - 1) / 10f; - for (int i = 1, bins = options.getBinsCount() - 1; i < bins; i++) { - assertEquals(i, options.getBinForSample(i * binSize)); - } - } - - @Test - public void testBinIndexForRangeEqual90() { - final int binCount = 10; - final int minValue = 100; - final int maxValue = 100000; - - Histogram.UniformOptions options = new Histogram.UniformOptions(binCount, minValue, - maxValue); - - // logging underflow sample - assertEquals(0, options.getBinForSample(minValue - 1)); - - // logging overflow sample - assertEquals(binCount + 1, options.getBinForSample(maxValue)); - assertEquals(binCount + 1, options.getBinForSample(maxValue + 1)); - - // logging min edge sample - assertEquals(1, options.getBinForSample(minValue)); - - // logging max edge sample - assertEquals(binCount, options.getBinForSample(maxValue - 1)); - - // logging single valid sample per bin - final int binSize = (maxValue - minValue) / binCount; - - for (int i = 0; i < binCount; i++) { - assertEquals(i + 1, options.getBinForSample(minValue + binSize * i)); - } - } -} diff --git a/data/etc/preinstalled-packages-platform-overlays.xml b/data/etc/preinstalled-packages-platform-overlays.xml index 99594336999b..2fd65dc29363 100644 --- a/data/etc/preinstalled-packages-platform-overlays.xml +++ b/data/etc/preinstalled-packages-platform-overlays.xml @@ -56,6 +56,9 @@ <install-in-user-type package="com.android.internal.systemui.navbar.transparent"> <install-in user-type="FULL" /> </install-in-user-type> + <install-in-user-type package="com.android.role.notes.enabled"> + <install-in user-type="FULL" /> + </install-in-user-type> <install-in-user-type package="com.android.theme.color.amethyst"> <install-in user-type="FULL" /> <install-in user-type="PROFILE" /> diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 25b074d20b81..2307d6080f9f 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -401,8 +401,9 @@ public final class Bitmap implements Parcelable { /** * This is called by methods that want to throw an exception if the bitmap * has already been recycled. + * @hide */ - private void checkRecycled(String errorMessage) { + void checkRecycled(String errorMessage) { if (mRecycled) { throw new IllegalStateException(errorMessage); } diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java index 2f6dd468511b..5c065775eea2 100644 --- a/graphics/java/android/graphics/BitmapShader.java +++ b/graphics/java/android/graphics/BitmapShader.java @@ -120,6 +120,7 @@ public class BitmapShader extends Shader { if (bitmap == null) { throw new IllegalArgumentException("Bitmap must be non-null"); } + bitmap.checkRecycled("Cannot create BitmapShader for recycled bitmap"); mBitmap = bitmap; mTileX = tileX; mTileY = tileY; @@ -188,6 +189,8 @@ public class BitmapShader extends Shader { /** @hide */ @Override protected long createNativeInstance(long nativeMatrix, boolean filterFromPaint) { + mBitmap.checkRecycled("BitmapShader's bitmap has been recycled"); + boolean enableLinearFilter = mFilterMode == FILTER_MODE_LINEAR; if (mFilterMode == FILTER_MODE_DEFAULT) { mFilterFromPaint = filterFromPaint; diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 0b29973507d2..56c3068fe5e9 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -2083,32 +2083,29 @@ public final class ImageDecoder implements AutoCloseable { } sIsP010SupportedForAV1Initialized = true; - - if (hasHardwareDecoder("video/av01")) { - sIsP010SupportedForAV1 = true; - return true; - } - - sIsP010SupportedForAV1 = Build.VERSION.DEVICE_INITIAL_SDK_INT - >= Build.VERSION_CODES.S; - return sIsP010SupportedForAV1; + return sIsP010SupportedForAV1 = isP010SupportedforMime("video/av01"); } } /** - * Checks if the device has hardware decoder for the target mime type. - */ - private static boolean hasHardwareDecoder(String mime) { - final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS); - for (MediaCodecInfo info : sMCL.getCodecInfos()) { - if (info.isEncoder() == false && info.isHardwareAccelerated()) { - try { - if (info.getCapabilitiesForType(mime) != null) { - return true; - } - } catch (IllegalArgumentException e) { - // mime is not supported - return false; + * Checks if the device supports decoding 10-bit for the given mime type. + */ + private static boolean isP010SupportedforMime(String mime) { + MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS); + for (MediaCodecInfo mediaCodecInfo : codecList.getCodecInfos()) { + if (mediaCodecInfo.isEncoder()) { + continue; + } + for (String mediaType : mediaCodecInfo.getSupportedTypes()) { + if (mediaType.equalsIgnoreCase(mime)) { + MediaCodecInfo.CodecCapabilities codecCapabilities = + mediaCodecInfo.getCapabilitiesForType(mediaType); + for (int i = 0; i < codecCapabilities.colorFormats.length; ++i) { + if (codecCapabilities.colorFormats[i] + == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUVP010) { + return true; + } + } } } } diff --git a/libs/WindowManager/Jetpack/Android.bp b/libs/WindowManager/Jetpack/Android.bp index a5b192cd7ceb..abe8f859f2fe 100644 --- a/libs/WindowManager/Jetpack/Android.bp +++ b/libs/WindowManager/Jetpack/Android.bp @@ -55,20 +55,6 @@ prebuilt_etc { } // Extensions -// NOTE: This module is still under active development and must not -// be used in production. Use 'androidx.window.sidecar' instead. -android_library_import { - name: "window-extensions", - aars: ["window-extensions-release.aar"], - sdk_version: "current", -} - -android_library_import { - name: "window-extensions-core", - aars: ["window-extensions-core-release.aar"], - sdk_version: "current", -} - java_library { name: "androidx.window.extensions", srcs: [ @@ -77,8 +63,8 @@ java_library { "src/androidx/window/common/**/*.java", ], static_libs: [ - "window-extensions", - "window-extensions-core", + "androidx.window.extensions_extensions-nodeps", + "androidx.window.extensions.core_core-nodeps", ], installable: true, sdk_version: "core_platform", diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java index 8386131b177d..a7a6b3c92157 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java @@ -122,16 +122,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent { addWindowLayoutInfoListener(activity, extConsumer); } - @Override - public void addWindowLayoutInfoListener(@NonNull @UiContext Context context, - @NonNull java.util.function.Consumer<WindowLayoutInfo> consumer) { - final Consumer<WindowLayoutInfo> extConsumer = consumer::accept; - synchronized (mLock) { - mJavaToExtConsumers.put(consumer, extConsumer); - } - addWindowLayoutInfoListener(context, extConsumer); - } - /** * Similar to {@link #addWindowLayoutInfoListener(Activity, java.util.function.Consumer)}, but * takes a UI Context as a parameter. diff --git a/libs/WindowManager/Jetpack/window-extensions-core-release.aar b/libs/WindowManager/Jetpack/window-extensions-core-release.aar Binary files differdeleted file mode 100644 index 96ff840b984b..000000000000 --- a/libs/WindowManager/Jetpack/window-extensions-core-release.aar +++ /dev/null diff --git a/libs/WindowManager/Jetpack/window-extensions-release.aar b/libs/WindowManager/Jetpack/window-extensions-release.aar Binary files differdeleted file mode 100644 index c3b6916121d0..000000000000 --- a/libs/WindowManager/Jetpack/window-extensions-release.aar +++ /dev/null diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java index e84a78f42616..133fd87a2f63 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java @@ -33,12 +33,19 @@ public interface BackAnimation { * * @param touchX the X touch position of the {@link MotionEvent}. * @param touchY the Y touch position of the {@link MotionEvent}. + * @param velocityX the X velocity computed from the {@link MotionEvent}. + * @param velocityY the Y velocity computed from the {@link MotionEvent}. * @param keyAction the original {@link KeyEvent#getAction()} when the event was dispatched to * the process. This is forwarded separately because the input pipeline may mutate * the {#event} action state later. * @param swipeEdge the edge from which the swipe begins. */ - void onBackMotion(float touchX, float touchY, int keyAction, + void onBackMotion( + float touchX, + float touchY, + float velocityX, + float velocityY, + int keyAction, @BackEvent.SwipeEdge int swipeEdge); /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index 210c9aab14d6..47d3a5c52074 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -256,8 +256,20 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont private class BackAnimationImpl implements BackAnimation { @Override public void onBackMotion( - float touchX, float touchY, int keyAction, @BackEvent.SwipeEdge int swipeEdge) { - mShellExecutor.execute(() -> onMotionEvent(touchX, touchY, keyAction, swipeEdge)); + float touchX, + float touchY, + float velocityX, + float velocityY, + int keyAction, + @BackEvent.SwipeEdge int swipeEdge + ) { + mShellExecutor.execute(() -> onMotionEvent( + /* touchX = */ touchX, + /* touchY = */ touchY, + /* velocityX = */ velocityX, + /* velocityY = */ velocityY, + /* keyAction = */ keyAction, + /* swipeEdge = */ swipeEdge)); } @Override @@ -332,13 +344,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * Called when a new motion event needs to be transferred to this * {@link BackAnimationController} */ - public void onMotionEvent(float touchX, float touchY, int keyAction, + public void onMotionEvent( + float touchX, + float touchY, + float velocityX, + float velocityY, + int keyAction, @BackEvent.SwipeEdge int swipeEdge) { if (mPostCommitAnimationInProgress) { return; } - mTouchTracker.update(touchX, touchY); + mTouchTracker.update(touchX, touchY, velocityX, velocityY); if (keyAction == MotionEvent.ACTION_DOWN) { if (!mBackGestureStarted) { mShouldStartOnNextMoveEvent = true; @@ -561,6 +578,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont } if (runner.isWaitingAnimation()) { ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Gesture released, but animation didn't ready."); + // Supposed it is in post commit animation state, and start the timeout to watch + // if the animation is ready. + mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); return; } else if (runner.isAnimationCancelled()) { invokeOrCancelBack(); @@ -577,6 +597,8 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont if (mPostCommitAnimationInProgress) { return; } + + mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startPostCommitAnimation()"); mPostCommitAnimationInProgress = true; mShellExecutor.executeDelayed(mAnimationTimeoutRunnable, MAX_ANIMATION_DURATION); @@ -595,9 +617,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont */ @VisibleForTesting void onBackAnimationFinished() { - if (!mPostCommitAnimationInProgress) { - return; - } // Stop timeout runner. mShellExecutor.removeCallbacks(mAnimationTimeoutRunnable); mPostCommitAnimationInProgress = false; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java index 695ef4e66302..904574b08562 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TouchTracker.java @@ -42,11 +42,13 @@ class TouchTracker { */ private float mInitTouchX; private float mInitTouchY; + private float mLatestVelocityX; + private float mLatestVelocityY; private float mStartThresholdX; private int mSwipeEdge; private boolean mCancelled; - void update(float touchX, float touchY) { + void update(float touchX, float touchY, float velocityX, float velocityY) { /** * If back was previously cancelled but the user has started swiping in the forward * direction again, restart back. @@ -58,6 +60,8 @@ class TouchTracker { } mLatestTouchX = touchX; mLatestTouchY = touchY; + mLatestVelocityX = velocityX; + mLatestVelocityY = velocityY; } void setTriggerBack(boolean triggerBack) { @@ -84,7 +88,14 @@ class TouchTracker { } BackMotionEvent createStartEvent(RemoteAnimationTarget target) { - return new BackMotionEvent(mInitTouchX, mInitTouchY, 0, mSwipeEdge, target); + return new BackMotionEvent( + /* touchX = */ mInitTouchX, + /* touchY = */ mInitTouchY, + /* progress = */ 0, + /* velocityX = */ 0, + /* velocityY = */ 0, + /* swipeEdge = */ mSwipeEdge, + /* departingAnimationTarget = */ target); } BackMotionEvent createProgressEvent() { @@ -111,7 +122,14 @@ class TouchTracker { } BackMotionEvent createProgressEvent(float progress) { - return new BackMotionEvent(mLatestTouchX, mLatestTouchY, progress, mSwipeEdge, null); + return new BackMotionEvent( + /* touchX = */ mLatestTouchX, + /* touchY = */ mLatestTouchY, + /* progress = */ progress, + /* velocityX = */ mLatestVelocityX, + /* velocityY = */ mLatestVelocityY, + /* swipeEdge = */ mSwipeEdge, + /* departingAnimationTarget = */ null); } public void setProgressThreshold(float progressThreshold) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 670b24c176b5..0400963a47e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -25,6 +25,7 @@ import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED import android.app.WindowConfiguration.WindowingMode import android.content.Context +import android.graphics.Point import android.graphics.Rect import android.os.IBinder import android.os.SystemProperties @@ -193,6 +194,21 @@ class DesktopTasksController( } } + + /** + * Move a task to fullscreen after being dragged from fullscreen and released back into + * status bar area + */ + fun cancelMoveToFreeform(task: RunningTaskInfo, startPosition: Point) { + val wct = WindowContainerTransaction() + addMoveToFullscreenChanges(wct, task.token) + if (Transitions.ENABLE_SHELL_TRANSITIONS) { + enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition) + } else { + shellTaskOrganizer.applyTransaction(wct) + } + } + fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) { val wct = WindowContainerTransaction() addMoveToFullscreenChanges(wct, task.token) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index 3df2340d4524..27eda16f4171 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -17,11 +17,13 @@ package com.android.wm.shell.desktopmode; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.app.ActivityManager; +import android.graphics.Point; import android.graphics.Rect; import android.os.IBinder; import android.view.SurfaceControl; @@ -55,6 +57,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition public static final int FREEFORM_ANIMATION_DURATION = 336; private final List<IBinder> mPendingTransitionTokens = new ArrayList<>(); + private Point mStartPosition; public EnterDesktopTaskTransitionHandler( Transitions transitions) { @@ -79,6 +82,17 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition mPendingTransitionTokens.add(token); } + /** + * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE + * @param wct WindowContainerTransaction for transition + * @param startPosition Position of task when transition is triggered + */ + public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct, + Point startPosition) { + mStartPosition = startPosition; + startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct); + } + @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startT, @@ -173,6 +187,37 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition return true; } + if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE + && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN + && mStartPosition != null) { + // This Transition animates a task to fullscreen after being dragged from the status + // bar and then released back into the status bar area + final SurfaceControl sc = change.getLeash(); + startT.setWindowCrop(sc, null); + startT.apply(); + + final ValueAnimator animator = new ValueAnimator(); + animator.setFloatValues(DRAG_FREEFORM_SCALE, 1f); + animator.setDuration(FREEFORM_ANIMATION_DURATION); + final SurfaceControl.Transaction t = mTransactionSupplier.get(); + animator.addUpdateListener(animation -> { + final float scale = animation.getAnimatedFraction(); + t.setPosition(sc, mStartPosition.x * (1 - scale), + mStartPosition.y * (1 - scale)); + t.setScale(sc, scale, scale); + t.apply(); + }); + animator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mTransitions.getMainExecutor().execute( + () -> finishCallback.onTransitionFinished(null, null)); + } + }); + animator.start(); + return true; + } + return false; } 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 f70df833cf4f..8c98c77a29ce 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 @@ -30,14 +30,17 @@ import android.app.TaskInfo; import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.Rect; +import android.os.SystemClock; import android.view.Surface; import android.view.SurfaceControl; import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; +import com.android.internal.protolog.common.ProtoLog; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.animation.Interpolators; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.transition.Transitions; import java.lang.annotation.Retention; @@ -61,6 +64,14 @@ public class PipAnimationController { @Retention(RetentionPolicy.SOURCE) public @interface AnimationType {} + /** + * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if + * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button + * navigation, then the alpha type is unexpected. So use a timeout to avoid applying wrong + * animation style to an unrelated task. + */ + private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 800; + public static final int TRANSITION_DIRECTION_NONE = 0; public static final int TRANSITION_DIRECTION_SAME = 1; public static final int TRANSITION_DIRECTION_TO_PIP = 2; @@ -109,6 +120,9 @@ public class PipAnimationController { }); private PipTransitionAnimator mCurrentAnimator; + @AnimationType + private int mOneShotAnimationType = ANIM_TYPE_BOUNDS; + private long mLastOneShotAlphaAnimationTime; public PipAnimationController(PipSurfaceTransactionHelper helper) { mSurfaceTransactionHelper = helper; @@ -222,6 +236,37 @@ public class PipAnimationController { } /** + * Sets the preferred enter animation type for one time. This is typically used to set the + * animation type to {@link PipAnimationController#ANIM_TYPE_ALPHA}. + * <p> + * For example, gesture navigation would first fade out the PiP activity, and the transition + * should be responsible to animate in (such as fade in) the PiP. + */ + public void setOneShotEnterAnimationType(@AnimationType int animationType) { + mOneShotAnimationType = animationType; + if (animationType == ANIM_TYPE_ALPHA) { + mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis(); + } + } + + /** Returns the preferred animation type and consumes the one-shot type if needed. */ + @AnimationType + public int takeOneShotEnterAnimationType() { + final int type = mOneShotAnimationType; + if (type == ANIM_TYPE_ALPHA) { + // Restore to default type. + mOneShotAnimationType = ANIM_TYPE_BOUNDS; + if (SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime + > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "Alpha animation is expired. Use bounds animation."); + return ANIM_TYPE_BOUNDS; + } + } + return type; + } + + /** * Additional callback interface for PiP animation */ public static class PipAnimationCallback { 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 a0bd064149d2..5670fe6eaeba 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 @@ -62,7 +62,6 @@ import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Rect; import android.os.RemoteException; -import android.os.SystemClock; import android.os.SystemProperties; import android.util.Log; import android.view.Choreographer; @@ -111,12 +110,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, DisplayController.OnDisplaysChangedListener, ShellTaskOrganizer.FocusListener { private static final String TAG = PipTaskOrganizer.class.getSimpleName(); private static final boolean DEBUG = false; - /** - * The alpha type is set for swiping to home. But the swiped task may not enter PiP. And if - * another task enters PiP by non-swipe ways, e.g. call API in foreground or switch to 3-button - * navigation, then the alpha type is unexpected. - */ - private static final int ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS = 1000; /** * The fixed start delay in ms when fading out the content overlay from bounds animation. @@ -301,8 +294,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private WindowContainerToken mToken; private SurfaceControl mLeash; protected PipTransitionState mPipTransitionState; - private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS; - private long mLastOneShotAlphaAnimationTime; private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; protected PictureInPictureParams mPictureInPictureParams; @@ -422,18 +413,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } /** - * Sets the preferred animation type for one time. - * This is typically used to set the animation type to - * {@link PipAnimationController#ANIM_TYPE_ALPHA}. - */ - public void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) { - mOneShotAnimationType = animationType; - if (animationType == ANIM_TYPE_ALPHA) { - mLastOneShotAlphaAnimationTime = SystemClock.uptimeMillis(); - } - } - - /** * Override if the PiP should always use a fade-in animation during PiP entry. * * @return true if the mOneShotAnimationType should always be @@ -733,26 +712,17 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, return; } - if (mOneShotAnimationType == ANIM_TYPE_ALPHA - && SystemClock.uptimeMillis() - mLastOneShotAlphaAnimationTime - > ONE_SHOT_ALPHA_ANIMATION_TIMEOUT_MS) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: Alpha animation is expired. Use bounds animation.", TAG); - mOneShotAnimationType = ANIM_TYPE_BOUNDS; - } - if (Transitions.ENABLE_SHELL_TRANSITIONS) { // For Shell transition, we will animate the window in PipTransition#startAnimation // instead of #onTaskAppeared. return; } - if (shouldAlwaysFadeIn()) { - mOneShotAnimationType = ANIM_TYPE_ALPHA; - } - + final int animationType = shouldAlwaysFadeIn() + ? ANIM_TYPE_ALPHA + : mPipAnimationController.takeOneShotEnterAnimationType(); if (mWaitForFixedRotation) { - onTaskAppearedWithFixedRotation(); + onTaskAppearedWithFixedRotation(animationType); return; } @@ -763,7 +733,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, Objects.requireNonNull(destinationBounds, "Missing destination bounds"); final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); - if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { + if (animationType == ANIM_TYPE_BOUNDS) { if (!shouldAttachMenuEarly()) { mPipMenuController.attach(mLeash); } @@ -773,16 +743,15 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, mEnterAnimationDuration, null /* updateBoundsCallback */); mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP); - } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + } else if (animationType == ANIM_TYPE_ALPHA) { enterPipWithAlphaAnimation(destinationBounds, mEnterAnimationDuration); - mOneShotAnimationType = ANIM_TYPE_BOUNDS; } else { - throw new RuntimeException("Unrecognized animation type: " + mOneShotAnimationType); + throw new RuntimeException("Unrecognized animation type: " + animationType); } } - private void onTaskAppearedWithFixedRotation() { - if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + private void onTaskAppearedWithFixedRotation(int animationType) { + if (animationType == ANIM_TYPE_ALPHA) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: Defer entering PiP alpha animation, fixed rotation is ongoing", TAG); // If deferred, hside the surface till fixed rotation is completed. @@ -791,7 +760,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, tx.setAlpha(mLeash, 0f); tx.show(mLeash); tx.apply(); - mOneShotAnimationType = ANIM_TYPE_BOUNDS; return; } final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds(); @@ -1895,7 +1863,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, + " binder=" + (mToken != null ? mToken.asBinder() : null)); pw.println(innerPrefix + "mLeash=" + mLeash); pw.println(innerPrefix + "mState=" + mPipTransitionState.getTransitionState()); - pw.println(innerPrefix + "mOneShotAnimationType=" + mOneShotAnimationType); pw.println(innerPrefix + "mPictureInPictureParams=" + mPictureInPictureParams); } 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 4a76a502462c..b743140b2403 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 @@ -87,7 +87,7 @@ public class PipTransition extends PipTransitionController { private final int mEnterExitAnimationDuration; private final PipSurfaceTransactionHelper mSurfaceTransactionHelper; private final Optional<SplitScreenController> mSplitScreenOptional; - private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS; + private @PipAnimationController.AnimationType int mEnterAnimationType = ANIM_TYPE_BOUNDS; private Transitions.TransitionFinishCallback mFinishCallback; private SurfaceControl.Transaction mFinishTransaction; private final Rect mExitDestinationBounds = new Rect(); @@ -133,20 +133,6 @@ public class PipTransition extends PipTransitionController { } @Override - public void setIsFullAnimation(boolean isFullAnimation) { - setOneShotAnimationType(isFullAnimation ? ANIM_TYPE_BOUNDS : ANIM_TYPE_ALPHA); - } - - /** - * Sets the preferred animation type for one time. - * This is typically used to set the animation type to - * {@link PipAnimationController#ANIM_TYPE_ALPHA}. - */ - private void setOneShotAnimationType(@PipAnimationController.AnimationType int animationType) { - mOneShotAnimationType = animationType; - } - - @Override public void startExitTransition(int type, WindowContainerTransaction out, @Nullable Rect destinationBounds) { if (destinationBounds != null) { @@ -288,7 +274,10 @@ public class PipTransition extends PipTransitionController { if (!requestHasPipEnter(request)) { throw new IllegalStateException("Called PiP augmentRequest when request has no PiP"); } - if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + mEnterAnimationType = mPipOrganizer.shouldAlwaysFadeIn() + ? ANIM_TYPE_ALPHA + : mPipAnimationController.takeOneShotEnterAnimationType(); + if (mEnterAnimationType == ANIM_TYPE_ALPHA) { mRequestedEnterTransition = transition; mRequestedEnterTask = request.getTriggerTask().token; outWCT.setActivityWindowingMode(request.getTriggerTask().token, @@ -308,7 +297,7 @@ public class PipTransition extends PipTransitionController { @Override public boolean handleRotateDisplay(int startRotation, int endRotation, WindowContainerTransaction wct) { - if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) { + if (mRequestedEnterTransition != null && mEnterAnimationType == ANIM_TYPE_ALPHA) { // A fade-in was requested but not-yet started. In this case, just recalculate the // initial state under the new rotation. int rotationDelta = deltaRotation(startRotation, endRotation); @@ -760,7 +749,6 @@ public class PipTransition extends PipTransitionController { if (taskInfo.pictureInPictureParams != null && taskInfo.pictureInPictureParams.isAutoEnterEnabled() && mPipTransitionState.getInSwipePipToHomeTransition()) { - mOneShotAnimationType = ANIM_TYPE_BOUNDS; final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay; startTransaction.setMatrix(leash, Matrix.IDENTITY_MATRIX, new float[9]) .setPosition(leash, destinationBounds.left, destinationBounds.top) @@ -796,17 +784,16 @@ public class PipTransition extends PipTransitionController { startTransaction.setMatrix(leash, tmpTransform, new float[9]); } - if (mPipOrganizer.shouldAlwaysFadeIn()) { - mOneShotAnimationType = ANIM_TYPE_ALPHA; - } - - if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + final int enterAnimationType = mEnterAnimationType; + if (enterAnimationType == ANIM_TYPE_ALPHA) { + // Restore to default type. + mEnterAnimationType = ANIM_TYPE_BOUNDS; startTransaction.setAlpha(leash, 0f); } startTransaction.apply(); PipAnimationController.PipTransitionAnimator animator; - if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) { + if (enterAnimationType == ANIM_TYPE_BOUNDS) { animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */, rotationDelta); @@ -829,13 +816,11 @@ public class PipTransition extends PipTransitionController { animator.setColorContentOverlay(mContext); } } - } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { + } else if (enterAnimationType == ANIM_TYPE_ALPHA) { animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, 0f, 1f); - mOneShotAnimationType = ANIM_TYPE_BOUNDS; } else { - throw new RuntimeException("Unrecognized animation type: " - + mOneShotAnimationType); + throw new RuntimeException("Unrecognized animation type: " + enterAnimationType); } animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) @@ -897,7 +882,7 @@ public class PipTransition extends PipTransitionController { .setWindowCrop(leash, endBounds.width(), endBounds.height()); } } - mSplitScreenOptional.get().finishEnterSplitScreen(startTransaction); + mSplitScreenOptional.get().finishEnterSplitScreen(finishTransaction); startTransaction.apply(); mPipOrganizer.onExitPipFinished(taskInfo); 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 f51e247fe112..7979ce7a80c1 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 @@ -105,15 +105,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH } /** - * Called to inform the transition that the animation should start with the assumption that - * PiP is not animating from its original bounds, but rather a continuation of another - * animation. For example, gesture navigation would first fade out the PiP activity, and the - * transition should be responsible to animate in (such as fade in) the PiP. - */ - public void setIsFullAnimation(boolean isFullAnimation) { - } - - /** * Called when the Shell wants to start an exit Pip transition/animation. */ public void startExitTransition(int type, WindowContainerTransaction out, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 463ad77d828f..b0bb14b49db6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -968,12 +968,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb mPipBoundsState.setShelfVisibility(visible, shelfHeight); } - private void setPinnedStackAnimationType(int animationType) { - mPipTaskOrganizer.setOneShotAnimationType(animationType); - mPipTransitionController.setIsFullAnimation( - animationType == PipAnimationController.ANIM_TYPE_BOUNDS); - } - @VisibleForTesting void setPinnedStackAnimationListener(PipAnimationListener callback) { mPinnedStackAnimationRecentsCallback = callback; @@ -1337,7 +1331,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb @Override public void setPipAnimationTypeToAlpha() { executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha", - (controller) -> controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA)); + (controller) -> controller.mPipAnimationController.setOneShotEnterAnimationType( + ANIM_TYPE_ALPHA)); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl index 81e118a31b73..f819bee2d5e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/ISplitScreen.aidl @@ -129,9 +129,10 @@ interface ISplitScreen { /** * Start a pair of intents in one transition. */ - oneway void startIntents(in PendingIntent pendingIntent1, in Bundle options1, - in PendingIntent pendingIntent2, in Bundle options2, int splitPosition, - float splitRatio, in RemoteTransition remoteTransition, in InstanceId instanceId) = 19; + oneway void startIntents(in PendingIntent pendingIntent1, in ShortcutInfo shortcutInfo1, + in Bundle options1, in PendingIntent pendingIntent2, in ShortcutInfo shortcutInfo2, + in Bundle options2, int splitPosition, float splitRatio, + in RemoteTransition remoteTransition, in InstanceId instanceId) = 19; /** * Blocking call that notifies and gets additional split-screen targets when entering 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 7d5ab8428a3e..2cd16be9590c 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 @@ -626,6 +626,35 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, splitPosition, splitRatio, adapter, instanceId); } + private void startIntents(PendingIntent pendingIntent1, + @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, + PendingIntent pendingIntent2, @Nullable ShortcutInfo shortcutInfo2, + @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + Intent fillInIntent1 = null; + Intent fillInIntent2 = null; + final String packageName1 = SplitScreenUtils.getPackageName(pendingIntent1); + final String packageName2 = SplitScreenUtils.getPackageName(pendingIntent2); + if (samePackage(packageName1, packageName2)) { + if (supportMultiInstancesSplit(packageName1)) { + fillInIntent1 = new Intent(); + fillInIntent1.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + fillInIntent2 = new Intent(); + fillInIntent2.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK"); + } else { + pendingIntent2 = null; + 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.startIntents(pendingIntent1, fillInIntent1, shortcutInfo1, options1, + pendingIntent2, fillInIntent2, shortcutInfo2, options2, splitPosition, splitRatio, + remoteTransition, instanceId); + } + @Override public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { @@ -1066,11 +1095,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, } @Override - public void startIntents(PendingIntent pendingIntent1, @Nullable Bundle options1, - PendingIntent pendingIntent2, @Nullable Bundle options2, + public void startIntents(PendingIntent pendingIntent1, @Nullable ShortcutInfo shortcutInfo1, + @Nullable Bundle options1, PendingIntent pendingIntent2, + @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, @SplitPosition int splitPosition, float splitRatio, @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { - // TODO(b/259368992): To be implemented. + executeRemoteCallWithTaskPermission(mController, "startIntents", + (controller) -> + controller.startIntents(pendingIntent1, shortcutInfo1, + options1, pendingIntent2, shortcutInfo2, 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 22800ad8e8a8..8b890bba20b6 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 @@ -22,6 +22,7 @@ import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; +import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN; import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED; import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString; @@ -155,8 +156,10 @@ class SplitScreenTransitions { } boolean isRootOrSplitSideRoot = change.getParent() == null || topRoot.equals(change.getParent()); - // For enter or exit, we only want to animate the side roots but not the top-root. - if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) { + boolean isDivider = change.getFlags() == FLAG_IS_DIVIDER_BAR; + // For enter or exit, we only want to animate side roots and the divider but not the + // top-root. + if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer()) || isDivider) { continue; } @@ -165,6 +168,10 @@ class SplitScreenTransitions { t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); t.setWindowCrop(leash, change.getEndAbsBounds().width(), change.getEndAbsBounds().height()); + } else if (isDivider) { + t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top); + t.setLayer(leash, Integer.MAX_VALUE); + t.show(leash); } boolean isOpening = isOpeningTransition(info); if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { 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 dd91a37039e4..ce5a2af65646 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 @@ -682,6 +682,46 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setEnterInstanceId(instanceId); } + void startIntents(PendingIntent pendingIntent1, Intent fillInIntent1, + @Nullable ShortcutInfo shortcutInfo1, @Nullable Bundle options1, + PendingIntent pendingIntent2, Intent fillInIntent2, + @Nullable ShortcutInfo shortcutInfo2, @Nullable Bundle options2, + @SplitPosition int splitPosition, float splitRatio, + @Nullable RemoteTransition remoteTransition, InstanceId instanceId) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + 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. + mMainStage.activate(wct, false /* reparent */); + } + + prepareEvictChildTasksIfSplitActive(wct); + mSplitLayout.setDivideRatio(splitRatio); + updateWindowBounds(mSplitLayout, wct); + wct.reorder(mRootTaskInfo.token, true); + setRootForceTranslucent(false, wct); + + setSideStagePosition(splitPosition, wct); + options1 = options1 != null ? options1 : new Bundle(); + addActivityOptions(options1, mSideStage); + if (shortcutInfo1 != null) { + wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1); + } else { + wct.sendPendingIntent(pendingIntent1, fillInIntent1, options1); + } + options2 = options2 != null ? options2 : new Bundle(); + addActivityOptions(options2, mMainStage); + if (shortcutInfo2 != null) { + wct.startShortcut(mContext.getPackageName(), shortcutInfo2, options2); + } else { + wct.sendPendingIntent(pendingIntent2, fillInIntent2, options2); + } + + mSplitTransitions.startEnterTransition( + TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null, null); + setEnterInstanceId(instanceId); + } + /** Starts a pair of tasks using legacy transition. */ void startTasksWithLegacyTransition(int taskId1, @Nullable Bundle options1, int taskId2, @Nullable Bundle options2, @SplitPosition int splitPosition, @@ -690,6 +730,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (options1 == null) options1 = new Bundle(); if (taskId2 == INVALID_TASK_ID) { // Launching a solo task. + // Exit split first if this task under split roots. + if (mMainStage.containsTask(taskId1) || mSideStage.containsTask(taskId1)) { + exitSplitScreen(null /* childrenToTop */, EXIT_REASON_RECREATE_SPLIT); + } ActivityOptions activityOptions = ActivityOptions.fromBundle(options1); activityOptions.update(ActivityOptions.makeRemoteAnimation(adapter)); options1 = activityOptions.toBundle(); @@ -891,10 +935,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSyncQueue.queue(wrapAsSplitRemoteAnimation(adapter), WindowManager.TRANSIT_OPEN, wct); } - mSyncQueue.runInSync(t -> { - setDividerVisibility(true, t); - }); - setEnterInstanceId(instanceId); } @@ -933,6 +973,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, @Override public void onAnimationCancelled(boolean isKeyguardOccluded) { onRemoteAnimationFinishedOrCancelled(evictWct); + setDividerVisibility(true, null); try { adapter.getRunner().onAnimationCancelled(isKeyguardOccluded); } catch (RemoteException e) { @@ -973,6 +1014,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, t.setPosition(apps[i].leash, 0, 0); } } + setDividerVisibility(true, t); t.apply(); IRemoteAnimationFinishedCallback wrapCallback = @@ -1463,6 +1505,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, void finishEnterSplitScreen(SurfaceControl.Transaction t) { mSplitLayout.init(); setDividerVisibility(true, t); + // Ensure divider surface are re-parented back into the hierarchy at the end of the + // transition. See Transition#buildFinishTransaction for more detail. + t.reparent(mSplitLayout.getDividerLeash(), mRootTaskLeash); + updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); t.show(mRootTaskLeash); setSplitsVisible(true); @@ -1772,6 +1818,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, setDividerVisibility(mainStageVisible, null); } + // Set divider visibility flag and try to apply it, the param transaction is used to apply. + // See applyDividerVisibility for more detail. private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) { if (visible == mDividerVisible) { return; @@ -1798,14 +1846,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, return; } - if (t != null) { - applyDividerVisibility(t); - } else { - mSyncQueue.runInSync(transaction -> applyDividerVisibility(transaction)); - } + applyDividerVisibility(t); } - private void applyDividerVisibility(SurfaceControl.Transaction t) { + // Apply divider visibility by current visibility flag. If param transaction is non-null, it + // will apply by that transaction, if it is null and visible, it will run a fade-in animation, + // otherwise hide immediately. + private void applyDividerVisibility(@Nullable SurfaceControl.Transaction t) { final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); if (dividerLeash == null) { ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, @@ -1822,7 +1869,12 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDividerFadeInAnimator.cancel(); } - if (mDividerVisible) { + mSplitLayout.getRefDividerBounds(mTempRect1); + if (t != null) { + t.setVisibility(dividerLeash, mDividerVisible); + t.setLayer(dividerLeash, Integer.MAX_VALUE); + t.setPosition(dividerLeash, mTempRect1.left, mTempRect1.top); + } else if (mDividerVisible) { final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f); mDividerFadeInAnimator.addUpdateListener(animation -> { @@ -1862,7 +1914,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mDividerFadeInAnimator.start(); } else { - t.hide(dividerLeash); + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + transaction.hide(dividerLeash); + transaction.apply(); + mTransactionPool.release(transaction); } } @@ -2446,7 +2501,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } finishEnterSplitScreen(finishT); - addDividerBarToTransition(info, finishT, true /* show */); + addDividerBarToTransition(info, true /* show */); return true; } @@ -2589,7 +2644,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) { // TODO: Have a proper remote for this. Until then, though, reset state and use the // normal animation stuff (which falls back to the normal launcher remote). - t.hide(mSplitLayout.getDividerLeash()); + setDividerVisibility(false, t); mSplitLayout.release(t); mSplitTransitions.mPendingDismiss = null; return false; @@ -2607,7 +2662,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, }); } - addDividerBarToTransition(info, finishT, false /* show */); + addDividerBarToTransition(info, false /* show */); return true; } @@ -2648,11 +2703,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, logExit(EXIT_REASON_UNKNOWN); } - private void addDividerBarToTransition(@NonNull TransitionInfo info, - @NonNull SurfaceControl.Transaction finishT, boolean show) { + private void addDividerBarToTransition(@NonNull TransitionInfo info, boolean show) { final SurfaceControl leash = mSplitLayout.getDividerLeash(); final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash); mSplitLayout.getRefDividerBounds(mTempRect1); + barChange.setParent(mRootTaskInfo.token); barChange.setStartAbsBounds(mTempRect1); barChange.setEndAbsBounds(mTempRect1); barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK); @@ -2660,15 +2715,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Technically this should be order-0, but this is running after layer assignment // and it's a special case, so just add to end. info.addChange(barChange); - - if (show) { - finishT.setLayer(leash, Integer.MAX_VALUE); - finishT.setPosition(leash, mTempRect1.left, mTempRect1.top); - finishT.show(leash); - // Ensure divider surface are re-parented back into the hierarchy at the end of the - // transition. See Transition#buildFinishTransaction for more detail. - finishT.reparent(leash, mRootTaskLeash); - } } RemoteAnimationTarget getDividerBarLegacyTarget() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index fa4de16b37f1..bdb7d44bad32 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -143,6 +143,10 @@ public class Transitions implements RemoteCallable<Transitions> { /** Transition type to fullscreen from desktop mode. */ public static final int TRANSIT_EXIT_DESKTOP_MODE = WindowManager.TRANSIT_FIRST_CUSTOM + 12; + /** Transition type to animate back to fullscreen when drag to freeform is cancelled. */ + public static final int TRANSIT_CANCEL_ENTERING_DESKTOP_MODE = + WindowManager.TRANSIT_FIRST_CUSTOM + 13; + private final WindowOrganizer mOrganizer; private final Context mContext; private final ShellExecutor mMainExecutor; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index 060dc4e05b46..dfde7e6feff5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -110,19 +110,11 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - final int outsetLeftId = R.dimen.freeform_resize_handle; - final int outsetTopId = R.dimen.freeform_resize_handle; - final int outsetRightId = R.dimen.freeform_resize_handle; - final int outsetBottomId = R.dimen.freeform_resize_handle; - mRelayoutParams.reset(); mRelayoutParams.mRunningTaskInfo = taskInfo; mRelayoutParams.mLayoutResId = R.layout.caption_window_decor; mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; mRelayoutParams.mShadowRadiusId = shadowRadiusID; - if (isDragResizeable) { - mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); - } relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index afc573e4fcbb..5226eeebcaa2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -34,6 +34,7 @@ import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.content.Context; import android.content.res.Resources; +import android.graphics.Point; import android.graphics.Rect; import android.hardware.input.InputManager; import android.os.Handler; @@ -557,8 +558,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragToDesktopAnimationStarted = false; return; } else if (mDragToDesktopAnimationStarted) { - mDesktopTasksController.ifPresent(c -> - c.moveToFullscreen(relevantDecor.mTaskInfo)); + Point startPosition = new Point((int) ev.getX(), (int) ev.getY()); + mDesktopTasksController.ifPresent( + c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, + startPosition)); mDragToDesktopAnimationStarted = false; return; } 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 f9c0e600dd38..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; @@ -208,11 +209,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final SurfaceControl oldDecorationSurface = mDecorationContainerSurface; final WindowContainerTransaction wct = new WindowContainerTransaction(); - final int outsetLeftId = R.dimen.freeform_resize_handle; - final int outsetTopId = R.dimen.freeform_resize_handle; - final int outsetRightId = R.dimen.freeform_resize_handle; - final int outsetBottomId = R.dimen.freeform_resize_handle; - final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId( taskInfo.getWindowingMode()); mRelayoutParams.reset(); @@ -220,9 +216,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin mRelayoutParams.mLayoutResId = windowDecorLayoutId; mRelayoutParams.mCaptionHeightId = R.dimen.freeform_decor_caption_height; mRelayoutParams.mShadowRadiusId = shadowRadiusID; - if (isDragResizeable) { - mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId); - } relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult); // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo @@ -319,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) { @@ -424,13 +416,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_controls_window_decor) { // Align the handle menu to the left of the caption. - menuX = mRelayoutParams.mCaptionX - mResult.mDecorContainerOffsetX + mMarginMenuStart; - menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuTop; + menuX = mRelayoutParams.mCaptionX + mMarginMenuStart; + menuY = mRelayoutParams.mCaptionY + mMarginMenuTop; } else { // Position the handle menu at the center of the caption. - menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2) - - mResult.mDecorContainerOffsetX; - menuY = mRelayoutParams.mCaptionY - mResult.mDecorContainerOffsetY + mMarginMenuStart; + menuX = mRelayoutParams.mCaptionX + (captionWidth / 2) - (mMenuWidth / 2); + menuY = mRelayoutParams.mCaptionY + mMarginMenuStart; } // App Info pill setup. @@ -497,23 +488,18 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin final boolean pointInAppInfoPill = pointInView( mHandleMenuAppInfoPill.mWindowViewHost.getView(), - inputPoint.x - mHandleMenuAppInfoPillPosition.x - mResult.mDecorContainerOffsetX, - inputPoint.y - mHandleMenuAppInfoPillPosition.y - - mResult.mDecorContainerOffsetY); + inputPoint.x - mHandleMenuAppInfoPillPosition.x, + inputPoint.y - mHandleMenuAppInfoPillPosition.y); boolean pointInWindowingPill = false; if (mHandleMenuWindowingPill != null) { pointInWindowingPill = pointInView(mHandleMenuWindowingPill.mWindowViewHost.getView(), - inputPoint.x - mHandleMenuWindowingPillPosition.x - - mResult.mDecorContainerOffsetX, - inputPoint.y - mHandleMenuWindowingPillPosition.y - - mResult.mDecorContainerOffsetY); + inputPoint.x - mHandleMenuWindowingPillPosition.x, + inputPoint.y - mHandleMenuWindowingPillPosition.y); } final boolean pointInMoreActionsPill = pointInView( mHandleMenuMoreActionsPill.mWindowViewHost.getView(), - inputPoint.x - mHandleMenuMoreActionsPillPosition.x - - mResult.mDecorContainerOffsetX, - inputPoint.y - mHandleMenuMoreActionsPillPosition.y - - mResult.mDecorContainerOffsetY); + inputPoint.x - mHandleMenuMoreActionsPillPosition.x, + inputPoint.y - mHandleMenuMoreActionsPillPosition.y); if (!pointInAppInfoPill && !pointInWindowingPill && !pointInMoreActionsPill && !pointInOpenMenuButton) { closeHandleMenu(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 8cb575cc96e3..d5437c72acac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -64,8 +64,8 @@ class DragResizeInputListener implements AutoCloseable { private final TaskResizeInputEventReceiver mInputEventReceiver; private final DragPositioningCallback mCallback; - private int mWidth; - private int mHeight; + private int mTaskWidth; + private int mTaskHeight; private int mResizeHandleThickness; private int mCornerSize; @@ -128,78 +128,84 @@ class DragResizeInputListener implements AutoCloseable { * This is also used to update the touch regions of this handler every event dispatched here is * a potential resize request. * - * @param width The width of the drag resize handler in pixels, including resize handle - * thickness. That is task width + 2 * resize handle thickness. - * @param height The height of the drag resize handler in pixels, including resize handle - * thickness. That is task height + 2 * resize handle thickness. + * @param taskWidth The width of the task. + * @param taskHeight The height of the task. * @param resizeHandleThickness The thickness of the resize handle in pixels. * @param cornerSize The size of the resize handle centered in each corner. * @param touchSlop The distance in pixels user has to drag with touch for it to register as * a resize action. */ - void setGeometry(int width, int height, int resizeHandleThickness, int cornerSize, + void setGeometry(int taskWidth, int taskHeight, int resizeHandleThickness, int cornerSize, int touchSlop) { - if (mWidth == width && mHeight == height + if (mTaskWidth == taskWidth && mTaskHeight == taskHeight && mResizeHandleThickness == resizeHandleThickness && mCornerSize == cornerSize) { return; } - mWidth = width; - mHeight = height; + mTaskWidth = taskWidth; + mTaskHeight = taskHeight; mResizeHandleThickness = resizeHandleThickness; mCornerSize = cornerSize; mDragDetector.setTouchSlop(touchSlop); Region touchRegion = new Region(); - final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness); + final Rect topInputBounds = new Rect( + -mResizeHandleThickness, + -mResizeHandleThickness, + mTaskWidth + mResizeHandleThickness, + 0); touchRegion.union(topInputBounds); - final Rect leftInputBounds = new Rect(0, mResizeHandleThickness, - mResizeHandleThickness, mHeight - mResizeHandleThickness); + final Rect leftInputBounds = new Rect( + -mResizeHandleThickness, + 0, + 0, + mTaskHeight); touchRegion.union(leftInputBounds); final Rect rightInputBounds = new Rect( - mWidth - mResizeHandleThickness, mResizeHandleThickness, - mWidth, mHeight - mResizeHandleThickness); + mTaskWidth, + 0, + mTaskWidth + mResizeHandleThickness, + mTaskHeight); touchRegion.union(rightInputBounds); - final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness, - mWidth, mHeight); + final Rect bottomInputBounds = new Rect( + -mResizeHandleThickness, + mTaskHeight, + mTaskWidth + mResizeHandleThickness, + mTaskHeight + mResizeHandleThickness); touchRegion.union(bottomInputBounds); // Set up touch areas in each corner. int cornerRadius = mCornerSize / 2; mLeftTopCornerBounds = new Rect( - mResizeHandleThickness - cornerRadius, - mResizeHandleThickness - cornerRadius, - mResizeHandleThickness + cornerRadius, - mResizeHandleThickness + cornerRadius - ); + -cornerRadius, + -cornerRadius, + cornerRadius, + cornerRadius); touchRegion.union(mLeftTopCornerBounds); mRightTopCornerBounds = new Rect( - mWidth - mResizeHandleThickness - cornerRadius, - mResizeHandleThickness - cornerRadius, - mWidth - mResizeHandleThickness + cornerRadius, - mResizeHandleThickness + cornerRadius - ); + mTaskWidth - cornerRadius, + -cornerRadius, + mTaskWidth + cornerRadius, + cornerRadius); touchRegion.union(mRightTopCornerBounds); mLeftBottomCornerBounds = new Rect( - mResizeHandleThickness - cornerRadius, - mHeight - mResizeHandleThickness - cornerRadius, - mResizeHandleThickness + cornerRadius, - mHeight - mResizeHandleThickness + cornerRadius - ); + -cornerRadius, + mTaskHeight - cornerRadius, + cornerRadius, + mTaskHeight + cornerRadius); touchRegion.union(mLeftBottomCornerBounds); mRightBottomCornerBounds = new Rect( - mWidth - mResizeHandleThickness - cornerRadius, - mHeight - mResizeHandleThickness - cornerRadius, - mWidth - mResizeHandleThickness + cornerRadius, - mHeight - mResizeHandleThickness + cornerRadius - ); + mTaskWidth - cornerRadius, + mTaskHeight - cornerRadius, + mTaskWidth + cornerRadius, + mTaskHeight + cornerRadius); touchRegion.union(mRightBottomCornerBounds); try { @@ -358,16 +364,16 @@ class DragResizeInputListener implements AutoCloseable { @TaskPositioner.CtrlType private int calculateResizeHandlesCtrlType(float x, float y) { int ctrlType = 0; - if (x < mResizeHandleThickness) { + if (x < 0) { ctrlType |= TaskPositioner.CTRL_TYPE_LEFT; } - if (x > mWidth - mResizeHandleThickness) { + if (x > mTaskWidth) { ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT; } - if (y < mResizeHandleThickness) { + if (y < 0) { ctrlType |= TaskPositioner.CTRL_TYPE_TOP; } - if (y > mHeight - mResizeHandleThickness) { + if (y > mTaskHeight) { ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM; } return ctrlType; 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 4ebd09fdecee..19a31822aabb 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; @@ -98,7 +99,6 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> private final Binder mOwner = new Binder(); private final Rect mCaptionInsetsRect = new Rect(); - private final Rect mTaskSurfaceCrop = new Rect(); private final float[] mTmpColor = new float[3]; WindowDecoration( @@ -193,13 +193,13 @@ 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); } // DecorationContainerSurface @@ -218,21 +218,14 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final Rect taskBounds = taskConfig.windowConfiguration.getBounds(); final Resources resources = mDecorWindowContext.getResources(); - outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId); - outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId); - outResult.mWidth = taskBounds.width() - + loadDimensionPixelSize(resources, params.mOutsetRightId) - - outResult.mDecorContainerOffsetX; - outResult.mHeight = taskBounds.height() - + loadDimensionPixelSize(resources, params.mOutsetBottomId) - - outResult.mDecorContainerOffsetY; - startT.setPosition( - mDecorationContainerSurface, - outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY) - .setWindowCrop(mDecorationContainerSurface, - outResult.mWidth, outResult.mHeight) + 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(); @@ -250,8 +243,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> 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()) + startT.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height()) .setShadowRadius(mTaskBackgroundSurface, shadowRadius) .setColor(mTaskBackgroundSurface, mTmpColor) .show(mTaskBackgroundSurface); @@ -269,11 +261,7 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId); final int captionWidth = taskBounds.width(); - startT.setPosition( - mCaptionContainerSurface, - -outResult.mDecorContainerOffsetX + params.mCaptionX, - -outResult.mDecorContainerOffsetY + params.mCaptionY) - .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) + startT.setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight) .show(mCaptionContainerSurface); if (mCaptionWindowManager == null) { @@ -314,14 +302,9 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> // Task surface itself Point taskPosition = mTaskInfo.positionInParent; - mTaskSurfaceCrop.set( - outResult.mDecorContainerOffsetX, - outResult.mDecorContainerOffsetY, - outResult.mWidth + outResult.mDecorContainerOffsetX, - outResult.mHeight + outResult.mDecorContainerOffsetY); startT.show(mTaskSurface); finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y) - .setCrop(mTaskSurface, mTaskSurfaceCrop); + .setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight); } /** @@ -400,18 +383,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) @@ -435,49 +420,27 @@ 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; int mCaptionWidthId; int mShadowRadiusId; - int mOutsetTopId; - int mOutsetBottomId; - int mOutsetLeftId; - int mOutsetRightId; - int mCaptionX; int mCaptionY; - void setOutsets(int leftId, int topId, int rightId, int bottomId) { - mOutsetLeftId = leftId; - mOutsetTopId = topId; - mOutsetRightId = rightId; - mOutsetBottomId = bottomId; - } - - void setCaptionPosition(int left, int top) { - mCaptionX = left; - mCaptionY = top; - } - void reset() { mLayoutResId = Resources.ID_NULL; mCaptionHeightId = Resources.ID_NULL; mCaptionWidthId = Resources.ID_NULL; mShadowRadiusId = Resources.ID_NULL; - mOutsetTopId = Resources.ID_NULL; - mOutsetBottomId = Resources.ID_NULL; - mOutsetLeftId = Resources.ID_NULL; - mOutsetRightId = Resources.ID_NULL; - mCaptionX = 0; mCaptionY = 0; } @@ -487,14 +450,10 @@ public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer> int mWidth; int mHeight; T mRootView; - int mDecorContainerOffsetX; - int mDecorContainerOffsetY; void reset() { mWidth = 0; mHeight = 0; - mDecorContainerOffsetX = 0; - mDecorContainerOffsetY = 0; mRootView = null; } } diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml index 67ca9a1a17f7..b6d92814ad43 100644 --- a/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml +++ b/libs/WindowManager/Shell/tests/flicker/AndroidTest.xml @@ -29,6 +29,7 @@ <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> <option name="teardown-command" value="settings delete system show_touches" /> <option name="teardown-command" value="settings delete system pointer_location" /> + <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 806bffebd4cb..d95c7a488ea1 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -166,6 +166,9 @@ public class BackAnimationControllerTest extends ShellTestCase { doMotionEvent(MotionEvent.ACTION_DOWN, 0); doMotionEvent(MotionEvent.ACTION_MOVE, 0); mController.setTriggerBack(true); + } + + private void releaseBackGesture() { doMotionEvent(MotionEvent.ACTION_UP, 0); } @@ -201,6 +204,8 @@ public class BackAnimationControllerTest extends ShellTestCase { .setOnBackNavigationDone(new RemoteCallback(result))); triggerBackGesture(); simulateRemoteAnimationStart(type); + mShellExecutor.flushAll(); + releaseBackGesture(); simulateRemoteAnimationFinished(); mShellExecutor.flushAll(); @@ -252,6 +257,7 @@ public class BackAnimationControllerTest extends ShellTestCase { createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, false); triggerBackGesture(); + releaseBackGesture(); verify(mAppCallback, times(1)).onBackInvoked(); @@ -269,6 +275,8 @@ public class BackAnimationControllerTest extends ShellTestCase { triggerBackGesture(); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + releaseBackGesture(); + // Check that back invocation is dispatched. verify(mAnimatorCallback).onBackInvoked(); verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any()); @@ -308,6 +316,9 @@ public class BackAnimationControllerTest extends ShellTestCase { triggerBackGesture(); simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME); + mShellExecutor.flushAll(); + + releaseBackGesture(); // Simulate transition timeout. mShellExecutor.flushAll(); @@ -369,6 +380,9 @@ public class BackAnimationControllerTest extends ShellTestCase { simulateRemoteAnimationStart(type); mShellExecutor.flushAll(); + releaseBackGesture(); + mShellExecutor.flushAll(); + assertTrue("Navigation Done callback not called for " + BackNavigationInfo.typeToString(type), result.mBackNavigationDone); assertTrue("TriggerBack should have been true", result.mTriggerBack); @@ -395,6 +409,8 @@ public class BackAnimationControllerTest extends ShellTestCase { .setOnBackNavigationDone(new RemoteCallback(result))); triggerBackGesture(); mShellExecutor.flushAll(); + releaseBackGesture(); + mShellExecutor.flushAll(); assertTrue("Navigation Done callback not called for " + BackNavigationInfo.typeToString(type), result.mBackNavigationDone); @@ -458,9 +474,12 @@ public class BackAnimationControllerTest extends ShellTestCase { private void doMotionEvent(int actionDown, int coordinate) { mController.onMotionEvent( - coordinate, coordinate, - actionDown, - BackEvent.EDGE_LEFT); + /* touchX */ coordinate, + /* touchY */ coordinate, + /* velocityX = */ 0, + /* velocityY = */ 0, + /* keyAction */ actionDown, + /* swipeEdge */ BackEvent.EDGE_LEFT); } private void simulateRemoteAnimationStart(int type) throws RemoteException { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java index 3608474bd90e..874ef80c29f0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackProgressAnimatorTest.java @@ -16,8 +16,6 @@ package com.android.wm.shell.back; -import static android.window.BackEvent.EDGE_LEFT; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -48,12 +46,21 @@ public class BackProgressAnimatorTest { private CountDownLatch mTargetProgressCalled = new CountDownLatch(1); private Handler mMainThreadHandler; + private BackMotionEvent backMotionEventFrom(float touchX, float progress) { + return new BackMotionEvent( + /* touchX = */ touchX, + /* touchY = */ 0, + /* progress = */ progress, + /* velocityX = */ 0, + /* velocityY = */ 0, + /* swipeEdge = */ BackEvent.EDGE_LEFT, + /* departingAnimationTarget = */ null); + } + @Before public void setUp() throws Exception { mMainThreadHandler = new Handler(Looper.getMainLooper()); - final BackMotionEvent backEvent = new BackMotionEvent( - 0, 0, - 0, EDGE_LEFT, null); + final BackMotionEvent backEvent = backMotionEventFrom(0, 0); mMainThreadHandler.post( () -> { mProgressAnimator = new BackProgressAnimator(); @@ -63,9 +70,7 @@ public class BackProgressAnimatorTest { @Test public void testBackProgressed() throws InterruptedException { - final BackMotionEvent backEvent = new BackMotionEvent( - 100, 0, - mTargetProgress, EDGE_LEFT, null); + final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress); mMainThreadHandler.post( () -> mProgressAnimator.onBackProgressed(backEvent)); @@ -78,9 +83,7 @@ public class BackProgressAnimatorTest { @Test public void testBackCancelled() throws InterruptedException { // Give the animator some progress. - final BackMotionEvent backEvent = new BackMotionEvent( - 100, 0, - mTargetProgress, EDGE_LEFT, null); + final BackMotionEvent backEvent = backMotionEventFrom(100, mTargetProgress); mMainThreadHandler.post( () -> mProgressAnimator.onBackProgressed(backEvent)); mTargetProgressCalled.await(1, TimeUnit.SECONDS); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java index ba9c159bad28..d62e6601723a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/TouchTrackerTest.java @@ -47,43 +47,45 @@ public class TouchTrackerTest { public void generatesProgress_leftEdge() { mTouchTracker.setGestureStartLocation(INITIAL_X_LEFT_EDGE, 0, BackEvent.EDGE_LEFT); float touchX = 10; + float velocityX = 0; + float velocityY = 0; // Pre-commit - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); // Post-commit touchX += 100; mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); // Cancel touchX -= 10; mTouchTracker.setTriggerBack(false); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Cancel more touchX -= 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Restart touchX += 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Restarted, but pre-commit float restartX = touchX; touchX += 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (touchX - restartX) / FAKE_THRESHOLD, 0f); // Restarted, post-commit touchX += 10; mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (touchX - INITIAL_X_LEFT_EDGE) / FAKE_THRESHOLD, 0f); } @@ -91,43 +93,45 @@ public class TouchTrackerTest { public void generatesProgress_rightEdge() { mTouchTracker.setGestureStartLocation(INITIAL_X_RIGHT_EDGE, 0, BackEvent.EDGE_RIGHT); float touchX = INITIAL_X_RIGHT_EDGE - 10; // Fake right edge + float velocityX = 0f; + float velocityY = 0f; // Pre-commit - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); // Post-commit touchX -= 100; mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); // Cancel touchX += 10; mTouchTracker.setTriggerBack(false); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Cancel more touchX += 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Restart touchX -= 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), 0, 0f); // Restarted, but pre-commit float restartX = touchX; touchX -= 10; - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (restartX - touchX) / FAKE_THRESHOLD, 0f); // Restarted, post-commit touchX -= 10; mTouchTracker.setTriggerBack(true); - mTouchTracker.update(touchX, 0); + mTouchTracker.update(touchX, 0, velocityX, velocityY); assertEquals(getProgress(), (INITIAL_X_RIGHT_EDGE - touchX) / FAKE_THRESHOLD, 0f); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java index 15bb10ed4f2b..842c699fa42d 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java @@ -262,7 +262,8 @@ public class PipTaskOrganizerTest extends ShellTestCase { DisplayLayout layout = new DisplayLayout(info, mContext.getResources(), true, true); mPipDisplayLayoutState.setDisplayLayout(layout); - mPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA); + doReturn(PipAnimationController.ANIM_TYPE_ALPHA).when(mMockPipAnimationController) + .takeOneShotEnterAnimationType(); mPipTaskOrganizer.setSurfaceControlTransactionFactory( MockSurfaceControlHelper::createMockSurfaceControlTransaction); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java index eda6fdc4dbd4..e6219d1aa792 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java @@ -113,6 +113,7 @@ public class StageCoordinatorTests extends ShellTestCase { private SurfaceSession mSurfaceSession = new SurfaceSession(); private SurfaceControl mRootLeash; + private SurfaceControl mDividerLeash; private ActivityManager.RunningTaskInfo mRootTask; private StageCoordinator mStageCoordinator; private Transitions mTransitions; @@ -129,12 +130,14 @@ public class StageCoordinatorTests extends ShellTestCase { mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mMainExecutor, Optional.empty())); + mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build(); when(mSplitLayout.getBounds1()).thenReturn(mBounds1); when(mSplitLayout.getBounds2()).thenReturn(mBounds2); when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds); when(mSplitLayout.isLandscape()).thenReturn(false); when(mSplitLayout.applyTaskChanges(any(), any(), any())).thenReturn(true); + when(mSplitLayout.getDividerLeash()).thenReturn(mDividerLeash); mRootTask = new TestRunningTaskInfoBuilder().build(); mRootLeash = new SurfaceControl.Builder(mSurfaceSession).setName("test").build(); 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 dfa3c1010eed..38a519af934b 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,6 +49,7 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowManager.LayoutParams; +import android.window.SurfaceSyncGroup; import android.window.TaskConstants; import android.window.WindowContainerTransaction; @@ -100,6 +101,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<>(); @@ -159,14 +162,8 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(false) .build(); taskInfo.isFocused = false; - // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is - // 64px. + // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -213,14 +210,8 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .build(); taskInfo.isFocused = true; - // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is - // 64px. + // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -229,8 +220,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(decorContainerSurfaceBuilder).setParent(taskSurface); verify(decorContainerSurfaceBuilder).setContainerLayer(); verify(mMockSurfaceControlStartT).setTrustedOverlay(decorContainerSurface, true); - verify(mMockSurfaceControlStartT).setPosition(decorContainerSurface, -20, -40); - verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 380, 220); + verify(mMockSurfaceControlStartT).setWindowCrop(decorContainerSurface, 300, 100); verify(taskBackgroundSurfaceBuilder).setParent(taskSurface); verify(taskBackgroundSurfaceBuilder).setEffectLayer(); @@ -244,7 +234,6 @@ public class WindowDecorationTests extends ShellTestCase { verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); verify(captionContainerSurfaceBuilder).setContainerLayer(); - verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40); verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); @@ -268,12 +257,12 @@ public class WindowDecorationTests extends ShellTestCase { verify(mMockSurfaceControlFinishT) .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y); verify(mMockSurfaceControlFinishT) - .setCrop(taskSurface, new Rect(-20, -40, 360, 180)); + .setWindowCrop(taskSurface, 300, 100); verify(mMockSurfaceControlStartT) .show(taskSurface); - assertEquals(380, mRelayoutResult.mWidth); - assertEquals(220, mRelayoutResult.mHeight); + assertEquals(300, mRelayoutResult.mWidth); + assertEquals(100, mRelayoutResult.mHeight); } @Test @@ -309,14 +298,8 @@ public class WindowDecorationTests extends ShellTestCase { .setVisible(true) .build(); taskInfo.isFocused = true; - // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is - // 64px. + // Density is 2. Shadow radius is 10px. Caption height is 64px. taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -419,11 +402,6 @@ public class WindowDecorationTests extends ShellTestCase { .build(); taskInfo.isFocused = true; taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); windowDecor.relayout(taskInfo); @@ -438,7 +416,7 @@ public class WindowDecorationTests extends ShellTestCase { verify(additionalWindowSurfaceBuilder).setContainerLayer(); verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface); verify(additionalWindowSurfaceBuilder).build(); - verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40); + verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 0, 0); final int width = WindowDecoration.loadDimensionPixelSize( mContext.getResources(), mCaptionMenuWidthId); final int height = WindowDecoration.loadDimensionPixelSize( @@ -496,11 +474,6 @@ public class WindowDecorationTests extends ShellTestCase { .build(); taskInfo.isFocused = true; taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2; - mRelayoutParams.setOutsets( - R.dimen.test_window_decor_left_outset, - R.dimen.test_window_decor_top_outset, - R.dimen.test_window_decor_right_outset, - R.dimen.test_window_decor_bottom_outset); final SurfaceControl taskSurface = mock(SurfaceControl.class); final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface); @@ -508,7 +481,6 @@ public class WindowDecorationTests extends ShellTestCase { verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface); verify(captionContainerSurfaceBuilder).setContainerLayer(); - verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40); // Width of the captionContainerSurface should match the width of TASK_BOUNDS verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64); verify(mMockSurfaceControlStartT).show(captionContainerSurface); @@ -584,9 +556,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 - mRelayoutResult.mDecorContainerOffsetX, - y - mRelayoutResult.mDecorContainerOffsetY, + mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, x, y, width, height, shadowRadius, cornerRadius); return additionalWindow; } diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp index 2a8cb42f7675..c4d3f5cedfa8 100644 --- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp @@ -53,6 +53,8 @@ SkiaOpenGLPipeline::~SkiaOpenGLPipeline() { } MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { + bool wasSurfaceless = mEglManager.isCurrent(EGL_NO_SURFACE); + // In case the surface was destroyed (e.g. a previous trimMemory call) we // need to recreate it here. if (mHardwareBuffer) { @@ -65,6 +67,37 @@ MakeCurrentResult SkiaOpenGLPipeline::makeCurrent() { if (!mEglManager.makeCurrent(mEglSurface, &error)) { return MakeCurrentResult::AlreadyCurrent; } + + // Make sure read/draw buffer state of default framebuffer is GL_BACK. Vendor implementations + // disagree on the draw/read buffer state if the default framebuffer transitions from a surface + // to EGL_NO_SURFACE and vice-versa. There was a related discussion within Khronos on this topic. + // See https://cvs.khronos.org/bugzilla/show_bug.cgi?id=13534. + // The discussion was not resolved with a clear consensus + if (error == 0 && wasSurfaceless && mEglSurface != EGL_NO_SURFACE) { + GLint curReadFB = 0; + GLint curDrawFB = 0; + glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &curReadFB); + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &curDrawFB); + + GLint buffer = GL_NONE; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glGetIntegerv(GL_DRAW_BUFFER0, &buffer); + if (buffer == GL_NONE) { + const GLenum drawBuffer = GL_BACK; + glDrawBuffers(1, &drawBuffer); + } + + glGetIntegerv(GL_READ_BUFFER, &buffer); + if (buffer == GL_NONE) { + glReadBuffer(GL_BACK); + } + + glBindFramebuffer(GL_READ_FRAMEBUFFER, curReadFB); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, curDrawFB); + + GL_CHECKPOINT(LOW); + } + return error ? MakeCurrentResult::Failed : MakeCurrentResult::Succeeded; } 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/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/java/android/media/soundtrigger/SoundTriggerInstrumentation.java b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java new file mode 100644 index 000000000000..80bc5c07dd66 --- /dev/null +++ b/media/java/android/media/soundtrigger/SoundTriggerInstrumentation.java @@ -0,0 +1,630 @@ +/** + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.media.soundtrigger; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.TestApi; +import android.hardware.soundtrigger.ConversionUtil; +import android.hardware.soundtrigger.SoundTrigger; +import android.media.soundtrigger_middleware.IAcknowledgeEvent; +import android.media.soundtrigger_middleware.IInjectGlobalEvent; +import android.media.soundtrigger_middleware.IInjectModelEvent; +import android.media.soundtrigger_middleware.IInjectRecognitionEvent; +import android.media.soundtrigger_middleware.ISoundTriggerInjection; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.ISoundTriggerService; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * Used to inject/observe events when using a fake SoundTrigger HAL for test purposes. + * Created by {@link SoundTriggerManager#getInjection(Executor, GlobalCallback)}. + * Only one instance of this class is valid at any given time, old instances will be delivered + * {@link GlobalCallback#onPreempted()}. + * @hide + */ +@TestApi +public final class SoundTriggerInstrumentation { + + private final Object mLock = new Object(); + @GuardedBy("mLock") + private IInjectGlobalEvent mInjectGlobalEvent = null; + + @GuardedBy("mLock") + private Map<IBinder, ModelSession> mModelSessionMap = new HashMap<>(); + @GuardedBy("mLock") + private Map<IBinder, RecognitionSession> mRecognitionSessionMap = new HashMap<>(); + @GuardedBy("mLock") + private IBinder mClientToken = null; + + private final GlobalCallback mClientCallback; + private final Executor mGlobalCallbackExecutor; + + /** + * Callback interface for un-sessioned events observed from the fake STHAL. + * Registered upon construction of {@link SoundTriggerInstrumentation} + * @hide + */ + @TestApi + public interface GlobalCallback { + /** + * Called when the created {@link SoundTriggerInstrumentation} object is invalidated + * by another client creating an {@link SoundTriggerInstrumentation} to instrument the + * fake STHAL. Only one client may inject at a time. + * All sessions are invalidated, no further events will be received, and no + * injected events will be delivered. + */ + default void onPreempted() {} + /** + * Called when the STHAL has been restarted by the framework, due to unexpected + * error conditions. + * Not called when {@link SoundTriggerInstrumentation#triggerRestart()} is injected. + */ + default void onRestarted() {} + /** + * Called when the framework detaches from the fake HAL. + * This is not transmitted to real HALs, but it indicates that the + * framework has flushed its global state. + */ + default void onFrameworkDetached() {} + /** + * Called when a client application attaches to the framework. + * This is not transmitted to real HALs, but it represents the state of + * the framework. + */ + default void onClientAttached() {} + /** + * Called when a client application detaches from the framework. + * This is not transmitted to real HALs, but it represents the state of + * the framework. + */ + default void onClientDetached() {} + /** + * Called when the fake HAL receives a model load from the framework. + * @param modelSession - A session which exposes additional injection + * functionality associated with the newly loaded + * model. See {@link ModelSession}. + */ + void onModelLoaded(@NonNull ModelSession modelSession); + } + + /** + * Callback for HAL events related to a loaded model. Register with + * {@link ModelSession#setModelCallback(Executor, ModelCallback)} + * Note, callbacks will not be delivered for events triggered by the injection. + * @hide + */ + @TestApi + public interface ModelCallback { + /** + * Called when the model associated with the {@link ModelSession} this callback + * was registered for was unloaded by the framework. + */ + default void onModelUnloaded() {} + /** + * Called when the model associated with the {@link ModelSession} this callback + * was registered for receives a set parameter call from the framework. + * @param param - Parameter being set. + * See {@link SoundTrigger.ModelParamTypes} + * @param value - Value the model parameter was set to. + */ + default void onParamSet(@SoundTrigger.ModelParamTypes int param, int value) {} + /** + * Called when the model associated with the {@link ModelSession} this callback + * was registered for receives a recognition start request. + * @param recognitionSession - A session which exposes additional injection + * functionality associated with the newly started + * recognition. See {@link RecognitionSession} + */ + void onRecognitionStarted(@NonNull RecognitionSession recognitionSession); + } + + /** + * Callback for HAL events related to a started recognition. Register with + * {@link RecognitionSession#setRecognitionCallback(Executor, RecognitionCallback)} + * Note, callbacks will not be delivered for events triggered by the injection. + * @hide + */ + @TestApi + public interface RecognitionCallback { + /** + * Called when the recognition associated with the {@link RecognitionSession} this + * callback was registered for was stopped by the framework. + */ + void onRecognitionStopped(); + } + + /** + * Session associated with a loaded model in the fake STHAL. + * Can be used to query details about the loaded model, register a callback for future + * model events, or trigger HAL events associated with a loaded model. + * This session is invalid once the model is unloaded, caused by a + * {@link ModelSession#triggerUnloadModel()}, + * the client unloading recognition, or if a {@link GlobalCallback#onRestarted()} is + * received. + * Further injections on an invalidated session will not be respected, and no future + * callbacks will be delivered. + * @hide + */ + @TestApi + public class ModelSession { + + /** + * Trigger the HAL to preemptively unload the model associated with this session. + * Typically occurs when a higher priority model is loaded which utilizes the same + * resources. + */ + public void triggerUnloadModel() { + synchronized (SoundTriggerInstrumentation.this.mLock) { + try { + mInjectModelEvent.triggerUnloadModel(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + mModelSessionMap.remove(mInjectModelEvent.asBinder()); + } + } + + /** + * Get the {@link SoundTriggerManager.Model} associated with this session. + * @return - The model associated with this session. + */ + public @NonNull SoundTriggerManager.Model getSoundModel() { + return mModel; + } + + /** + * Get the list of {@link SoundTrigger.Keyphrase} associated with this session. + * @return - The keyphrases associated with this session. + */ + public @NonNull List<SoundTrigger.Keyphrase> getPhrases() { + if (mPhrases == null) { + return new ArrayList<>(); + } else { + return new ArrayList<>(Arrays.asList(mPhrases)); + } + } + + /** + * Get whether this model is of keyphrase type. + * @return - true if the model is a keyphrase model, false otherwise + */ + public boolean isKeyphrase() { + return (mPhrases != null); + } + + /** + * Registers the model callback associated with this session. Events associated + * with this model session will be reported via this callback. + * See {@link ModelCallback} + * @param executor - Executor which the callback is dispatched on + * @param callback - Model callback for reporting model session events. + */ + public void setModelCallback(@NonNull @CallbackExecutor Executor executor, @NonNull + ModelCallback callback) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + mModelCallback = Objects.requireNonNull(callback); + mModelExecutor = Objects.requireNonNull(executor); + } + } + + /** + * Clear the model callback associated with this session, if any has been + * set by {@link #setModelCallback(Executor, ModelCallback)}. + */ + public void clearModelCallback() { + synchronized (SoundTriggerInstrumentation.this.mLock) { + mModelCallback = null; + mModelExecutor = null; + } + } + + private ModelSession(SoundModel model, Phrase[] phrases, + IInjectModelEvent injection) { + mModel = SoundTriggerManager.Model.create(UUID.fromString(model.uuid), + UUID.fromString(model.vendorUuid), + ConversionUtil.sharedMemoryToByteArray(model.data, model.dataSize)); + if (phrases != null) { + mPhrases = new SoundTrigger.Keyphrase[phrases.length]; + int i = 0; + for (var phrase : phrases) { + mPhrases[i++] = ConversionUtil.aidl2apiPhrase(phrase); + } + } else { + mPhrases = null; + } + mInjectModelEvent = injection; + } + + private void wrap(Consumer<ModelCallback> consumer) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + if (mModelCallback != null && mModelExecutor != null) { + final ModelCallback callback = mModelCallback; + mModelExecutor.execute(() -> consumer.accept(callback)); + } + } + } + + private final SoundTriggerManager.Model mModel; + private final SoundTrigger.Keyphrase[] mPhrases; + private final IInjectModelEvent mInjectModelEvent; + + @GuardedBy("SoundTriggerInstrumentation.this.mLock") + private ModelCallback mModelCallback = null; + @GuardedBy("SoundTriggerInstrumentation.this.mLock") + private Executor mModelExecutor = null; + } + + /** + * Session associated with a recognition start in the fake STHAL. + * Can be used to get information about the started recognition, register a callback + * for future events associated with this recognition, and triggering + * recognition events or aborts. + * This session is invalid once the recognition is stopped, caused by a + * {@link RecognitionSession#triggerAbortRecognition()}, + * {@link RecognitionSession#triggerRecognitionEvent(byte[], List)}, + * the client stopping recognition, or any operation which invalidates the + * {@link ModelSession} which the session was created from. + * Further injections on an invalidated session will not be respected, and no future + * callbacks will be delivered. + * @hide + */ + @TestApi + public class RecognitionSession { + + /** + * Get an integer token representing the audio session associated with this + * recognition in the STHAL. + * @return - The session token. + */ + public int getAudioSession() { + return mAudioSession; + } + + /** + * Get the recognition config used to start this recognition. + * @return - The config passed to the HAL for startRecognition. + */ + public @NonNull SoundTrigger.RecognitionConfig getRecognitionConfig() { + return mRecognitionConfig; + } + + /** + * Trigger a recognition in the fake STHAL. + * @param data - The opaque data buffer included in the recognition event. + * @param phraseExtras - Keyphrase metadata included in the event. The + * event must include metadata for the keyphrase id + * associated with this model to be received by the + * client application. + */ + public void triggerRecognitionEvent(@NonNull byte[] data, @Nullable + List<SoundTrigger.KeyphraseRecognitionExtra> phraseExtras) { + PhraseRecognitionExtra[] converted = null; + if (phraseExtras != null) { + converted = new PhraseRecognitionExtra[phraseExtras.size()]; + int i = 0; + for (var phraseExtra : phraseExtras) { + converted[i++] = ConversionUtil.api2aidlPhraseRecognitionExtra(phraseExtra); + } + } + synchronized (SoundTriggerInstrumentation.this.mLock) { + mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder()); + try { + mInjectRecognitionEvent.triggerRecognitionEvent(data, converted); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Trigger an abort recognition event in the fake HAL. This represents a + * preemptive ending of the recognition session by the HAL, despite no + * recognition detection. Typically occurs during contention for microphone + * usage, or if model limits are hit. + * See {@link SoundTriggerInstrumentation#setResourceContention(boolean)} to block + * subsequent downward calls for contention reasons. + */ + public void triggerAbortRecognition() { + synchronized (SoundTriggerInstrumentation.this.mLock) { + mRecognitionSessionMap.remove(mInjectRecognitionEvent.asBinder()); + try { + mInjectRecognitionEvent.triggerAbortRecognition(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Registers the recognition callback associated with this session. Events associated + * with this recognition session will be reported via this callback. + * See {@link RecognitionCallback} + * @param executor - Executor which the callback is dispatched on + * @param callback - Recognition callback for reporting recognition session events. + */ + public void setRecognitionCallback(@NonNull @CallbackExecutor Executor executor, + @NonNull RecognitionCallback callback) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + mRecognitionCallback = callback; + mRecognitionExecutor = executor; + } + } + + /** + * Clear the recognition callback associated with this session, if any has been + * set by {@link #setRecognitionCallback(Executor, RecognitionCallback)}. + */ + public void clearRecognitionCallback() { + synchronized (SoundTriggerInstrumentation.this.mLock) { + mRecognitionCallback = null; + mRecognitionExecutor = null; + } + } + + private RecognitionSession(int audioSession, + RecognitionConfig recognitionConfig, + IInjectRecognitionEvent injectRecognitionEvent) { + mAudioSession = audioSession; + mRecognitionConfig = ConversionUtil.aidl2apiRecognitionConfig(recognitionConfig); + mInjectRecognitionEvent = injectRecognitionEvent; + } + + private void wrap(Consumer<RecognitionCallback> consumer) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + if (mRecognitionCallback != null && mRecognitionExecutor != null) { + final RecognitionCallback callback = mRecognitionCallback; + mRecognitionExecutor.execute(() -> consumer.accept(callback)); + } + } + } + + private final int mAudioSession; + private final SoundTrigger.RecognitionConfig mRecognitionConfig; + private final IInjectRecognitionEvent mInjectRecognitionEvent; + + @GuardedBy("SoundTriggerInstrumentation.this.mLock") + private Executor mRecognitionExecutor = null; + @GuardedBy("SoundTriggerInstrumentation.this.mLock") + private RecognitionCallback mRecognitionCallback = null; + } + + // Implementation of injection interface passed to the HAL. + // This class will re-associate events received on this callback interface + // with sessions, to avoid staleness issues. + private class Injection extends ISoundTriggerInjection.Stub { + @Override + public void registerGlobalEventInjection(IInjectGlobalEvent globalInjection) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + mInjectGlobalEvent = globalInjection; + } + } + + @Override + public void onSoundModelLoaded(SoundModel model, @Nullable Phrase[] phrases, + IInjectModelEvent modelInjection, IInjectGlobalEvent globalSession) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return; + ModelSession modelSession = new ModelSession(model, phrases, modelInjection); + mModelSessionMap.put(modelInjection.asBinder(), modelSession); + mGlobalCallbackExecutor.execute(() -> mClientCallback.onModelLoaded(modelSession)); + } + } + + @Override + public void onSoundModelUnloaded(IInjectModelEvent modelSession) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + ModelSession clientModelSession = mModelSessionMap.remove(modelSession.asBinder()); + if (clientModelSession == null) return; + clientModelSession.wrap((ModelCallback cb) -> cb.onModelUnloaded()); + } + } + + @Override + public void onRecognitionStarted(int audioSessionHandle, RecognitionConfig config, + IInjectRecognitionEvent recognitionInjection, IInjectModelEvent modelSession) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder()); + if (clientModelSession == null) return; + RecognitionSession recogSession = new RecognitionSession( + audioSessionHandle, config, recognitionInjection); + mRecognitionSessionMap.put(recognitionInjection.asBinder(), recogSession); + clientModelSession.wrap((ModelCallback cb) -> + cb.onRecognitionStarted(recogSession)); + } + } + + @Override + public void onRecognitionStopped(IInjectRecognitionEvent recognitionSession) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + RecognitionSession clientRecognitionSession = + mRecognitionSessionMap.remove(recognitionSession.asBinder()); + if (clientRecognitionSession == null) return; + clientRecognitionSession.wrap((RecognitionCallback cb) + -> cb.onRecognitionStopped()); + } + } + + @Override + public void onParamSet(int modelParam, int value, IInjectModelEvent modelSession) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + ModelSession clientModelSession = mModelSessionMap.get(modelSession.asBinder()); + if (clientModelSession == null) return; + clientModelSession.wrap((ModelCallback cb) -> cb.onParamSet(modelParam, value)); + } + } + + + @Override + public void onRestarted(IInjectGlobalEvent globalSession) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return; + mRecognitionSessionMap.clear(); + mModelSessionMap.clear(); + mGlobalCallbackExecutor.execute(() -> mClientCallback.onRestarted()); + } + } + + @Override + public void onFrameworkDetached(IInjectGlobalEvent globalSession) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return; + mGlobalCallbackExecutor.execute(() -> mClientCallback.onFrameworkDetached()); + } + } + + @Override + public void onClientAttached(IBinder token, IInjectGlobalEvent globalSession) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + if (globalSession.asBinder() != mInjectGlobalEvent.asBinder()) return; + mClientToken = token; + mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientAttached()); + } + } + + @Override + public void onClientDetached(IBinder token) { + synchronized (SoundTriggerInstrumentation.this.mLock) { + if (token != mClientToken) return; + mClientToken = null; + mGlobalCallbackExecutor.execute(() -> mClientCallback.onClientDetached()); + } + } + + @Override + public void onPreempted() { + // This is always valid, independent of session + mGlobalCallbackExecutor.execute(() -> mClientCallback.onPreempted()); + // Callbacks will no longer be delivered, and injection will be silently dropped. + } + } + + /** + * @hide + */ + @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) + public SoundTriggerInstrumentation(ISoundTriggerService service, + @CallbackExecutor @NonNull Executor executor, + @NonNull GlobalCallback callback) { + mClientCallback = Objects.requireNonNull(callback); + mGlobalCallbackExecutor = Objects.requireNonNull(executor); + try { + service.attachInjection(new Injection()); + } catch (RemoteException e) { + e.rethrowFromSystemServer(); + } + } + + /** + * Simulate a HAL restart, typically caused by the framework on an unexpected error, + * or a restart of the core audio HAL. + * Application sessions will be detached, and all state will be cleared. The framework + * will re-attach to the HAL following restart. + * @hide + */ + @TestApi + public void triggerRestart() { + synchronized (mLock) { + if (mInjectGlobalEvent == null) { + throw new IllegalStateException( + "Attempted to trigger HAL restart before registration"); + } + try { + mInjectGlobalEvent.triggerRestart(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Trigger a resource available callback from the fake SoundTrigger HAL to the framework. + * This callback notifies the framework that methods which previously failed due to + * resource contention may now succeed. + * @hide + */ + @TestApi + public void triggerOnResourcesAvailable() { + synchronized (mLock) { + if (mInjectGlobalEvent == null) { + throw new IllegalStateException( + "Attempted to trigger HAL resources available before registration"); + } + try { + mInjectGlobalEvent.triggerOnResourcesAvailable(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Simulate resource contention, similar to when HAL which does not + * support concurrent capture opens a capture stream, or when a HAL + * has reached its maximum number of models. + * Subsequent model loads and recognition starts will gracefully error. + * Since this call does not trigger a callback through the framework, the + * call will block until the fake HAL has acknowledged the state change. + * @param isResourceContended - true to enable contention, false to return + * to normal functioning. + * @hide + */ + @TestApi + public void setResourceContention(boolean isResourceContended) { + synchronized (mLock) { + if (mInjectGlobalEvent == null) { + throw new IllegalStateException("Injection interface not set up"); + } + IInjectGlobalEvent current = mInjectGlobalEvent; + final CountDownLatch signal = new CountDownLatch(1); + try { + current.setResourceContention(isResourceContended, new IAcknowledgeEvent.Stub() { + @Override + public void eventReceived() { + signal.countDown(); + } + }); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + + // Block until we get a callback from the service that our request was serviced. + try { + // Rely on test timeout if we don't get a response. + signal.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + } +} + diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java index ae8121a59abf..c41bd1bc3094 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerManager.java +++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java @@ -18,11 +18,13 @@ package android.media.soundtrigger; import static android.hardware.soundtrigger.SoundTrigger.STATUS_ERROR; +import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -45,6 +47,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.ParcelUuid; import android.os.RemoteException; +import android.os.ServiceManager; import android.provider.Settings; import android.util.Slog; @@ -53,9 +56,9 @@ import com.android.internal.app.ISoundTriggerSession; import com.android.internal.util.Preconditions; import java.util.HashMap; -import java.util.List; import java.util.Objects; import java.util.UUID; +import java.util.concurrent.Executor; /** * This class provides management of non-voice (general sound trigger) based sound recognition @@ -609,4 +612,24 @@ public final class SoundTriggerManager { throw e.rethrowFromSystemServer(); } } + + /** + * Create a {@link SoundTriggerInstrumentation} for test purposes, which instruments a fake + * STHAL. Clients must attach to the appropriate underlying ST module. + * @param executor - Executor to dispatch global callbacks on + * @param callback - Callback for unsessioned events received by the fake STHAL + * @return - A {@link SoundTriggerInstrumentation} for observation/injection. + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) + @NonNull + public static SoundTriggerInstrumentation attachInstrumentation( + @CallbackExecutor @NonNull Executor executor, + @NonNull SoundTriggerInstrumentation.GlobalCallback callback) { + ISoundTriggerService service = ISoundTriggerService.Stub.asInterface( + ServiceManager.getService(Context.SOUND_TRIGGER_SERVICE)); + return new SoundTriggerInstrumentation(service, executor, callback); + } + } diff --git a/media/java/android/media/voice/KeyphraseModelManager.java b/media/java/android/media/voice/KeyphraseModelManager.java index 8ec8967a353e..5a690a57dbb2 100644 --- a/media/java/android/media/voice/KeyphraseModelManager.java +++ b/media/java/android/media/voice/KeyphraseModelManager.java @@ -21,7 +21,9 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.hardware.soundtrigger.SoundTrigger; +import android.os.Binder; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Slog; @@ -154,4 +156,23 @@ public final class KeyphraseModelManager { throw e.rethrowFromSystemServer(); } } + + /** + * Override the persistent enrolled model database with an in-memory + * fake for testing purposes. + * + * @param enabled - {@code true} if the model enrollment database should be overridden with an + * in-memory fake. {@code false} if the real, persistent model enrollment database should be + * used. + * @hide + */ + @RequiresPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) + @TestApi + public void setModelDatabaseForTestEnabled(boolean enabled) { + try { + mVoiceInteractionManagerService.setModelDatabaseForTestEnabled(enabled, new Binder()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } 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/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 ca891294576b..e8d3b1f45be3 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt @@ -252,13 +252,6 @@ class GetFlowUtils { )) } is PublicKeyCredentialEntry -> { - val passkeyUsername = credentialEntry.username.toString() - val passkeyDisplayName = credentialEntry.displayName?.toString() ?: "" - val (username, displayName) = userAndDisplayNameForPasskey( - passkeyUsername = passkeyUsername, - passkeyDisplayName = passkeyDisplayName, - ) - result.add(CredentialEntryInfo( providerId = providerId, providerDisplayName = providerLabel, @@ -268,8 +261,8 @@ class GetFlowUtils { fillInIntent = it.frameworkExtrasIntent, credentialType = CredentialType.PASSKEY, credentialTypeDisplayName = credentialEntry.typeDisplayName.toString(), - userName = username, - displayName = displayName, + userName = credentialEntry.username.toString(), + displayName = credentialEntry.displayName?.toString(), icon = credentialEntry.icon.loadDrawable(context), shouldTintIcon = credentialEntry.isDefaultIcon, lastUsedTimeMillis = credentialEntry.lastUsedTime, @@ -471,6 +464,9 @@ class CreateFlowUtils { createCredentialRequest.candidateQueryData, createCredentialRequest.isSystemProviderRequired ) + val appPreferredDefaultProviderId: String? = + if (!requestInfo.hasPermissionToOverrideDefault()) null + else createCredentialRequestJetpack?.displayInfo?.preferDefaultProvider return when (createCredentialRequestJetpack) { is CreatePasswordRequest -> RequestDisplayInfo( createCredentialRequestJetpack.id, @@ -479,6 +475,7 @@ class CreateFlowUtils { appLabel, context.getDrawable(R.drawable.ic_password_24) ?: return null, preferImmediatelyAvailableCredentials = false, + appPreferredDefaultProviderId = appPreferredDefaultProviderId, ) is CreatePublicKeyCredentialRequest -> { newRequestDisplayInfoFromPasskeyJson( @@ -487,6 +484,7 @@ class CreateFlowUtils { context = context, preferImmediatelyAvailableCredentials = createCredentialRequestJetpack.preferImmediatelyAvailableCredentials, + appPreferredDefaultProviderId = appPreferredDefaultProviderId, ) } is CreateCustomCredentialRequest -> { @@ -502,6 +500,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 @@ -511,20 +510,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()) { @@ -543,12 +549,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, @@ -674,6 +682,7 @@ class CreateFlowUtils { appLabel: String, context: Context, preferImmediatelyAvailableCredentials: Boolean, + appPreferredDefaultProviderId: String?, ): RequestDisplayInfo? { val json = JSONObject(requestJson) var passkeyUsername = "" @@ -694,6 +703,7 @@ class CreateFlowUtils { appLabel, context.getDrawable(R.drawable.ic_passkey_24) ?: return null, preferImmediatelyAvailableCredentials, + appPreferredDefaultProviderId, ) } } @@ -707,7 +717,7 @@ class CreateFlowUtils { * 2) username on top if display-name is not available. * 3) don't show username on second line if username == display-name */ -private fun userAndDisplayNameForPasskey( +fun userAndDisplayNameForPasskey( passkeyUsername: String, passkeyDisplayName: String, ): Pair<String, String> { diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt index 14bf4f23384b..2df0c7a9b1e8 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/ui/SectionHeader.kt @@ -27,8 +27,12 @@ import androidx.compose.ui.unit.dp import com.android.credentialmanager.ui.theme.LocalAndroidColorScheme @Composable -fun CredentialListSectionHeader(text: String) { - InternalSectionHeader(text, LocalAndroidColorScheme.current.colorOnSurfaceVariant) +fun CredentialListSectionHeader(text: String, isFirstSection: Boolean) { + InternalSectionHeader( + text = text, + color = LocalAndroidColorScheme.current.colorOnSurfaceVariant, + applyTopPadding = !isFirstSection + ) } @Composable @@ -37,8 +41,10 @@ fun MoreAboutPasskeySectionHeader(text: String) { } @Composable -private fun InternalSectionHeader(text: String, color: Color) { - Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 8.dp)) { +private fun InternalSectionHeader(text: String, color: Color, applyTopPadding: Boolean = false) { + Row(modifier = Modifier.fillMaxWidth().wrapContentHeight().padding( + top = if (applyTopPadding) 8.dp else 0.dp + )) { SectionHeaderText( text, modifier = Modifier.padding(top = 20.dp, bottom = 8.dp), diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt index 66d7db896247..96010cc66821 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt @@ -435,7 +435,7 @@ fun MoreOptionsRowIntroCard( } SheetContainerCard { item { HeadlineIcon(imageVector = Icons.Outlined.NewReleases) } - item { Divider(thickness = 24.dp, color = Color.Transparent) } + item { Divider(thickness = 16.dp, color = Color.Transparent) } item { HeadlineText( text = stringResource( @@ -633,6 +633,7 @@ fun MoreAboutPasskeysIntroCard( } } item { + Divider(thickness = 8.dp, color = Color.Transparent) MoreAboutPasskeySectionHeader( text = stringResource(R.string.public_key_cryptography_title) ) @@ -641,6 +642,7 @@ fun MoreAboutPasskeysIntroCard( } } item { + Divider(thickness = 8.dp, color = Color.Transparent) MoreAboutPasskeySectionHeader( text = stringResource(R.string.improved_account_security_title) ) @@ -649,6 +651,7 @@ fun MoreAboutPasskeysIntroCard( } } item { + Divider(thickness = 8.dp, color = Color.Transparent) MoreAboutPasskeySectionHeader( text = stringResource(R.string.seamless_transition_title) ) 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/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index c1ea1d8d1746..98bd020b234b 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -66,6 +66,7 @@ import com.android.credentialmanager.common.ui.HeadlineIcon import com.android.credentialmanager.common.ui.LargeLabelTextOnSurfaceVariant import com.android.credentialmanager.common.ui.Snackbar import com.android.credentialmanager.logging.GetCredentialEvent +import com.android.credentialmanager.userAndDisplayNameForPasskey import com.android.internal.logging.UiEventLogger.UiEventEnum @Composable @@ -322,12 +323,15 @@ fun AllSignInOptionCard( bottomPadding = 0.dp, ) }) { + var isFirstSection = true // For username items(sortedUserNameToCredentialEntryList) { item -> PerUserNameCredentials( perUserNameCredentialEntryList = item, onEntrySelected = onEntrySelected, + isFirstSection = isFirstSection, ) + isFirstSection = false } // Locked password manager if (authenticationEntryList.isNotEmpty()) { @@ -335,7 +339,9 @@ fun AllSignInOptionCard( LockedCredentials( authenticationEntryList = authenticationEntryList, onEntrySelected = onEntrySelected, + isFirstSection = isFirstSection, ) + isFirstSection = false } } // From another device @@ -345,15 +351,19 @@ fun AllSignInOptionCard( RemoteEntryCard( remoteEntry = remoteEntry, onEntrySelected = onEntrySelected, + isFirstSection = isFirstSection, ) + isFirstSection = false } } // Manage sign-ins (action chips) item { ActionChips( providerInfoList = providerInfoList, - onEntrySelected = onEntrySelected + onEntrySelected = onEntrySelected, + isFirstSection = isFirstSection, ) + isFirstSection = false } } onLog(GetCredentialEvent.CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD) @@ -366,6 +376,7 @@ fun AllSignInOptionCard( fun ActionChips( providerInfoList: List<ProviderInfo>, onEntrySelected: (BaseEntry) -> Unit, + isFirstSection: Boolean, ) { val actionChips = providerInfoList.flatMap { it.actionEntryList } if (actionChips.isEmpty()) { @@ -373,7 +384,8 @@ fun ActionChips( } CredentialListSectionHeader( - text = stringResource(R.string.get_dialog_heading_manage_sign_ins) + text = stringResource(R.string.get_dialog_heading_manage_sign_ins), + isFirstSection = isFirstSection, ) CredentialContainerCard { Column(verticalArrangement = Arrangement.spacedBy(2.dp)) { @@ -388,9 +400,11 @@ fun ActionChips( fun RemoteEntryCard( remoteEntry: RemoteEntryInfo, onEntrySelected: (BaseEntry) -> Unit, + isFirstSection: Boolean, ) { CredentialListSectionHeader( - text = stringResource(R.string.get_dialog_heading_from_another_device) + text = stringResource(R.string.get_dialog_heading_from_another_device), + isFirstSection = isFirstSection, ) CredentialContainerCard { Column( @@ -412,9 +426,11 @@ fun RemoteEntryCard( fun LockedCredentials( authenticationEntryList: List<AuthenticationEntryInfo>, onEntrySelected: (BaseEntry) -> Unit, + isFirstSection: Boolean, ) { CredentialListSectionHeader( - text = stringResource(R.string.get_dialog_heading_locked_password_managers) + text = stringResource(R.string.get_dialog_heading_locked_password_managers), + isFirstSection = isFirstSection, ) CredentialContainerCard { Column( @@ -432,11 +448,13 @@ fun LockedCredentials( fun PerUserNameCredentials( perUserNameCredentialEntryList: PerUserNameCredentialEntryList, onEntrySelected: (BaseEntry) -> Unit, + isFirstSection: Boolean, ) { CredentialListSectionHeader( text = stringResource( R.string.get_dialog_heading_for_username, perUserNameCredentialEntryList.userName - ) + ), + isFirstSection = isFirstSection, ) CredentialContainerCard { Column( @@ -457,6 +475,10 @@ fun CredentialEntryRow( enforceOneLine: Boolean = false, onTextLayout: (TextLayoutResult) -> Unit = {}, ) { + val (username, displayName) = if (credentialEntryInfo.credentialType == CredentialType.PASSKEY) + userAndDisplayNameForPasskey( + credentialEntryInfo.userName, credentialEntryInfo.displayName ?: "") + else Pair(credentialEntryInfo.userName, credentialEntryInfo.displayName) Entry( onClick = { onEntrySelected(credentialEntryInfo) }, iconImageBitmap = credentialEntryInfo.icon?.toBitmap()?.asImageBitmap(), @@ -465,13 +487,13 @@ fun CredentialEntryRow( iconPainter = if (credentialEntryInfo.icon == null) painterResource(R.drawable.ic_other_sign_in_24) else null, - entryHeadlineText = credentialEntryInfo.userName, + entryHeadlineText = username, entrySecondLineText = if ( credentialEntryInfo.credentialType == CredentialType.PASSWORD) { "••••••••••••" } else { val itemsToDisplay = listOf( - credentialEntryInfo.displayName, + displayName, credentialEntryInfo.credentialTypeDisplayName, credentialEntryInfo.providerDisplayName ).filterNot(TextUtils::isEmpty) 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/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/CustomizedAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt index 47ac2df67c76..e07a6298a627 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/CustomizedAppBar.kt @@ -69,6 +69,7 @@ import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp @@ -460,6 +461,9 @@ private fun TopAppBarLayout( ProvideTextStyle(value = titleTextStyle) { CompositionLocalProvider( LocalContentColor provides titleContentColor, + // Disable the title font scaling by only passing the density but not the + // font scale. + LocalDensity provides Density(density = LocalDensity.current.density), content = title ) } 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/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt index 5342def0003d..2c3e58ece8e0 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListRepository.kt @@ -20,9 +20,12 @@ import android.content.Context import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager +import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.ResolveInfo import com.android.internal.R +import com.android.settingslib.spaprivileged.framework.common.userManager import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine @@ -33,7 +36,11 @@ import kotlinx.coroutines.runBlocking */ internal interface AppListRepository { /** Loads the list of [ApplicationInfo]. */ - suspend fun loadApps(userId: Int, showInstantApps: Boolean): List<ApplicationInfo> + suspend fun loadApps( + userId: Int, + showInstantApps: Boolean = false, + matchAnyUserForAdmin: Boolean = false, + ): List<ApplicationInfo> /** Gets the flow of predicate that could used to filter system app. */ fun showSystemPredicate( @@ -61,10 +68,12 @@ object AppListRepositoryUtil { internal class AppListRepositoryImpl(private val context: Context) : AppListRepository { private val packageManager = context.packageManager + private val userManager = context.userManager override suspend fun loadApps( userId: Int, showInstantApps: Boolean, + matchAnyUserForAdmin: Boolean, ): List<ApplicationInfo> = coroutineScope { val hiddenSystemModulesDeferred = async { packageManager.getInstalledModules(0) @@ -75,12 +84,8 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo val hideWhenDisabledPackagesDeferred = async { context.resources.getStringArray(R.array.config_hideWhenDisabled_packageNames) } - val flags = PackageManager.ApplicationInfoFlags.of( - (PackageManager.MATCH_DISABLED_COMPONENTS or - PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() - ) val installedApplicationsAsUser = - packageManager.getInstalledApplicationsAsUser(flags, userId) + getInstalledApplications(userId, matchAnyUserForAdmin) val hiddenSystemModules = hiddenSystemModulesDeferred.await() val hideWhenDisabledPackages = hideWhenDisabledPackagesDeferred.await() @@ -89,6 +94,46 @@ internal class AppListRepositoryImpl(private val context: Context) : AppListRepo } } + private suspend fun getInstalledApplications( + userId: Int, + matchAnyUserForAdmin: Boolean, + ): List<ApplicationInfo> { + val regularFlags = ApplicationInfoFlags.of( + (PackageManager.MATCH_DISABLED_COMPONENTS or + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS).toLong() + ) + return if (!matchAnyUserForAdmin || !userManager.getUserInfo(userId).isAdmin) { + packageManager.getInstalledApplicationsAsUser(regularFlags, userId) + } else { + coroutineScope { + val deferredPackageNamesInChildProfiles = + userManager.getProfileIdsWithDisabled(userId) + .filter { it != userId } + .map { + async { + packageManager.getInstalledApplicationsAsUser(regularFlags, it) + .map { it.packageName } + } + } + val adminFlags = ApplicationInfoFlags.of( + PackageManager.MATCH_ANY_USER.toLong() or regularFlags.value + ) + val allInstalledApplications = + packageManager.getInstalledApplicationsAsUser(adminFlags, userId) + val packageNamesInChildProfiles = deferredPackageNamesInChildProfiles + .awaitAll() + .flatten() + .toSet() + // If an app is for a child profile and not installed on the owner, not display as + // 'not installed for this user' in the owner. This will prevent duplicates of work + // only apps showing up in the personal profile. + allInstalledApplications.filter { + it.installed || it.packageName !in packageNamesInChildProfiles + } + } + } + } + override fun showSystemPredicate( userIdFlow: Flow<Int>, showSystemFlow: Flow<Boolean>, diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt index 889604209b08..bd99ebdfa3e5 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/AppListViewModel.kt @@ -80,12 +80,15 @@ internal open class AppListViewModelImpl<T : AppRecord>( private val scope = viewModelScope + Dispatchers.IO private val userSubGraphsFlow = appListConfig.flow.map { config -> - config.userIds.map { userId -> UserSubGraph(userId, config.showInstantApps) } + config.userIds.map { userId -> + UserSubGraph(userId, config.showInstantApps, config.matchAnyUserForAdmin) + } }.shareIn(scope = scope, started = SharingStarted.Eagerly, replay = 1) private inner class UserSubGraph( private val userId: Int, private val showInstantApps: Boolean, + private val matchAnyUserForAdmin: Boolean, ) { private val userIdFlow = flowOf(userId) @@ -110,7 +113,8 @@ internal open class AppListViewModelImpl<T : AppRecord>( fun reloadApps() { scope.launch { - appsStateFlow.value = appListRepository.loadApps(userId, showInstantApps) + appsStateFlow.value = + appListRepository.loadApps(userId, showInstantApps, matchAnyUserForAdmin) } } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt index f4a6b59d4c4b..066db3475b36 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt @@ -62,6 +62,7 @@ private const val CONTENT_TYPE_HEADER = "header" data class AppListConfig( val userIds: List<Int>, val showInstantApps: Boolean, + val matchAnyUserForAdmin: Boolean, ) data class AppListState( diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt index 2ebbe8aab809..89bfa0eb646b 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt @@ -38,6 +38,7 @@ fun <T : AppRecord> AppListPage( title: String, listModel: AppListModel<T>, showInstantApps: Boolean = false, + matchAnyUserForAdmin: Boolean = false, primaryUserOnly: Boolean = false, noItemMessage: String? = null, moreOptions: @Composable MoreOptionsScope.() -> Unit = {}, @@ -59,6 +60,7 @@ fun <T : AppRecord> AppListPage( config = AppListConfig( userIds = userGroup.userInfos.map { it.id }, showInstantApps = showInstantApps, + matchAnyUserForAdmin = matchAnyUserForAdmin, ), listModel = listModel, state = AppListState( diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt index 57972edc6d3a..b732a6a4495e 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListRepositoryTest.kt @@ -23,9 +23,13 @@ import android.content.pm.PackageManager import android.content.pm.PackageManager.ApplicationInfoFlags import android.content.pm.PackageManager.ResolveInfoFlags import android.content.pm.ResolveInfo +import android.content.pm.UserInfo import android.content.res.Resources +import android.os.UserManager +import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.internal.R +import com.android.settingslib.spaprivileged.framework.common.userManager import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first @@ -35,10 +39,13 @@ 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.any import org.mockito.Mockito.anyInt import org.mockito.Mockito.eq +import org.mockito.Mockito.verify +import org.mockito.Spy import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule import org.mockito.Mockito.`when` as whenever @@ -49,8 +56,8 @@ class AppListRepositoryTest { @get:Rule val mockito: MockitoRule = MockitoJUnit.rule() - @Mock - private lateinit var context: Context + @Spy + private val context: Context = ApplicationProvider.getApplicationContext() @Mock private lateinit var resources: Resources @@ -58,6 +65,9 @@ class AppListRepositoryTest { @Mock private lateinit var packageManager: PackageManager + @Mock + private lateinit var userManager: UserManager + private lateinit var repository: AppListRepository @Before @@ -66,36 +76,116 @@ class AppListRepositoryTest { whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(emptyArray()) whenever(context.packageManager).thenReturn(packageManager) + whenever(context.userManager).thenReturn(userManager) whenever(packageManager.getInstalledModules(anyInt())).thenReturn(emptyList()) whenever( - packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID)) + packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), anyInt()) ).thenReturn(emptyList()) + whenever(userManager.getUserInfo(ADMIN_USER_ID)).thenReturn(UserInfo().apply { + flags = UserInfo.FLAG_ADMIN + }) + whenever(userManager.getProfileIdsWithDisabled(ADMIN_USER_ID)) + .thenReturn(intArrayOf(ADMIN_USER_ID, MANAGED_PROFILE_USER_ID)) repository = AppListRepositoryImpl(context) } - private fun mockInstalledApplications(apps: List<ApplicationInfo>) { + private fun mockInstalledApplications(apps: List<ApplicationInfo>, userId: Int) { whenever( - packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(USER_ID)) + packageManager.getInstalledApplicationsAsUser(any<ApplicationInfoFlags>(), eq(userId)) ).thenReturn(apps) } @Test fun loadApps_notShowInstantApps() = runTest { - mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP)) + mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID) - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) + val appList = repository.loadApps( + userId = ADMIN_USER_ID, + showInstantApps = false, + ) - assertThat(appListFlow).containsExactly(NORMAL_APP) + assertThat(appList).containsExactly(NORMAL_APP) } @Test fun loadApps_showInstantApps() = runTest { - mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP)) + mockInstalledApplications(listOf(NORMAL_APP, INSTANT_APP), ADMIN_USER_ID) + + val appList = repository.loadApps( + userId = ADMIN_USER_ID, + showInstantApps = true, + ) + + assertThat(appList).containsExactly(NORMAL_APP, INSTANT_APP) + } + + @Test + fun loadApps_notMatchAnyUserForAdmin_withRegularFlags() = runTest { + mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID) + + val appList = repository.loadApps( + userId = ADMIN_USER_ID, + matchAnyUserForAdmin = false, + ) + + assertThat(appList).containsExactly(NORMAL_APP) + val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java) + verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID)) + assertThat(flags.value.value).isEqualTo( + PackageManager.MATCH_DISABLED_COMPONENTS or + PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS + ) + } - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = true) + @Test + fun loadApps_matchAnyUserForAdmin_withMatchAnyUserFlag() = runTest { + mockInstalledApplications(listOf(NORMAL_APP), ADMIN_USER_ID) + + val appList = repository.loadApps( + userId = ADMIN_USER_ID, + matchAnyUserForAdmin = true, + ) + + assertThat(appList).containsExactly(NORMAL_APP) + val flags = ArgumentCaptor.forClass(ApplicationInfoFlags::class.java) + verify(packageManager).getInstalledApplicationsAsUser(flags.capture(), eq(ADMIN_USER_ID)) + assertThat(flags.value.value and PackageManager.MATCH_ANY_USER.toLong()).isGreaterThan(0L) + } + + @Test + fun loadApps_matchAnyUserForAdminAndInstalledOnManagedProfileOnly_notDisplayed() = runTest { + val managedProfileOnlyPackageName = "installed.on.managed.profile.only" + mockInstalledApplications(listOf(ApplicationInfo().apply { + packageName = managedProfileOnlyPackageName + }), ADMIN_USER_ID) + mockInstalledApplications(listOf(ApplicationInfo().apply { + packageName = managedProfileOnlyPackageName + flags = ApplicationInfo.FLAG_INSTALLED + }), MANAGED_PROFILE_USER_ID) + + val appList = repository.loadApps( + userId = ADMIN_USER_ID, + matchAnyUserForAdmin = true, + ) - assertThat(appListFlow).containsExactly(NORMAL_APP, INSTANT_APP) + assertThat(appList).isEmpty() + } + + @Test + fun loadApps_matchAnyUserForAdminAndInstalledOnSecondaryUserOnly_displayed() = runTest { + val secondaryUserOnlyApp = ApplicationInfo().apply { + packageName = "installed.on.secondary.user.only" + } + mockInstalledApplications(listOf(secondaryUserOnlyApp), ADMIN_USER_ID) + mockInstalledApplications(emptyList(), MANAGED_PROFILE_USER_ID) + + val appList = repository.loadApps( + userId = ADMIN_USER_ID, + matchAnyUserForAdmin = true, + ) + + assertThat(appList).containsExactly(secondaryUserOnlyApp) } @Test @@ -106,11 +196,11 @@ class AppListRepositoryTest { } whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(arrayOf(app.packageName)) - mockInstalledApplications(listOf(app)) + mockInstalledApplications(listOf(app), ADMIN_USER_ID) - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) + val appList = repository.loadApps(userId = ADMIN_USER_ID) - assertThat(appListFlow).isEmpty() + assertThat(appList).isEmpty() } @Test @@ -122,11 +212,11 @@ class AppListRepositoryTest { } whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(arrayOf(app.packageName)) - mockInstalledApplications(listOf(app)) + mockInstalledApplications(listOf(app), ADMIN_USER_ID) - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) + val appList = repository.loadApps(userId = ADMIN_USER_ID) - assertThat(appListFlow).isEmpty() + assertThat(appList).isEmpty() } @Test @@ -137,11 +227,11 @@ class AppListRepositoryTest { } whenever(resources.getStringArray(R.array.config_hideWhenDisabled_packageNames)) .thenReturn(arrayOf(app.packageName)) - mockInstalledApplications(listOf(app)) + mockInstalledApplications(listOf(app), ADMIN_USER_ID) - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) + val appList = repository.loadApps(userId = ADMIN_USER_ID) - assertThat(appListFlow).containsExactly(app) + assertThat(appList).containsExactly(app) } @Test @@ -151,11 +241,11 @@ class AppListRepositoryTest { enabled = false enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER } - mockInstalledApplications(listOf(app)) + mockInstalledApplications(listOf(app), ADMIN_USER_ID) - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) + val appList = repository.loadApps(userId = ADMIN_USER_ID) - assertThat(appListFlow).containsExactly(app) + assertThat(appList).containsExactly(app) } @Test @@ -164,11 +254,11 @@ class AppListRepositoryTest { packageName = "disabled" enabled = false } - mockInstalledApplications(listOf(app)) + mockInstalledApplications(listOf(app), ADMIN_USER_ID) - val appListFlow = repository.loadApps(userId = USER_ID, showInstantApps = false) + val appList = repository.loadApps(userId = ADMIN_USER_ID) - assertThat(appListFlow).isEmpty() + assertThat(appList).isEmpty() } @Test @@ -219,7 +309,11 @@ class AppListRepositoryTest { val app = IN_LAUNCHER_APP whenever( - packageManager.queryIntentActivitiesAsUser(any(), any<ResolveInfoFlags>(), eq(USER_ID)) + packageManager.queryIntentActivitiesAsUser( + any(), + any<ResolveInfoFlags>(), + eq(ADMIN_USER_ID) + ) ).thenReturn(listOf(resolveInfoOf(packageName = app.packageName))) val showSystemPredicate = getShowSystemPredicate(showSystem = false) @@ -229,12 +323,16 @@ class AppListRepositoryTest { @Test fun getSystemPackageNames_returnExpectedValues() = runTest { - mockInstalledApplications(listOf( - NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP)) + mockInstalledApplications( + apps = listOf( + NORMAL_APP, INSTANT_APP, SYSTEM_APP, UPDATED_SYSTEM_APP, HOME_APP, IN_LAUNCHER_APP + ), + userId = ADMIN_USER_ID, + ) val systemPackageNames = AppListRepositoryUtil.getSystemPackageNames( context = context, - userId = USER_ID, + userId = ADMIN_USER_ID, showInstantApps = false, ) @@ -243,12 +341,13 @@ class AppListRepositoryTest { private suspend fun getShowSystemPredicate(showSystem: Boolean) = repository.showSystemPredicate( - userIdFlow = flowOf(USER_ID), + userIdFlow = flowOf(ADMIN_USER_ID), showSystemFlow = flowOf(showSystem), ).first() private companion object { - const val USER_ID = 0 + const val ADMIN_USER_ID = 0 + const val MANAGED_PROFILE_USER_ID = 11 val NORMAL_APP = ApplicationInfo().apply { packageName = "normal" diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt index 4f0cddef078b..36d9db5ef251 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/AppListViewModelTest.kt @@ -85,7 +85,11 @@ class AppListViewModelTest { } private object FakeAppListRepository : AppListRepository { - override suspend fun loadApps(userId: Int, showInstantApps: Boolean) = listOf(APP) + override suspend fun loadApps( + userId: Int, + showInstantApps: Boolean, + matchAnyUserForAdmin: Boolean, + ) = listOf(APP) override fun showSystemPredicate( userIdFlow: Flow<Int>, @@ -112,7 +116,11 @@ class AppListViewModelTest { const val USER_ID = 0 const val PACKAGE_NAME = "package.name" const val LABEL = "Label" - val CONFIG = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false) + val CONFIG = AppListConfig( + userIds = listOf(USER_ID), + showInstantApps = false, + matchAnyUserForAdmin = false, + ) val APP = ApplicationInfo().apply { packageName = PACKAGE_NAME } diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt index a99d02de0df0..241a134b6a76 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt @@ -114,7 +114,11 @@ class AppListTest { ) { composeTestRule.setContent { AppListInput( - config = AppListConfig(userIds = listOf(USER_ID), showInstantApps = false), + config = AppListConfig( + userIds = listOf(USER_ID), + showInstantApps = false, + matchAnyUserForAdmin = false, + ), listModel = TestAppListModel(enableGrouping = enableGrouping), state = AppListState( showSystem = false.toState(), 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/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java index a3db6d7e17ff..e28ada4771c9 100644 --- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatterySaverUtils.java @@ -120,9 +120,9 @@ public class BatterySaverUtils { * @return true if the request succeeded. */ public static synchronized boolean setPowerSaveMode(Context context, - boolean enable, boolean needFirstTimeWarning) { + boolean enable, boolean needFirstTimeWarning, @SaverManualEnabledReason int reason) { if (DEBUG) { - Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF")); + Log.d(TAG, "Battery saver turning " + (enable ? "ON" : "OFF") + ", reason: " + reason); } final ContentResolver cr = context.getContentResolver(); @@ -152,6 +152,7 @@ public class BatterySaverUtils { sendSystemUiBroadcast(context, ACTION_SHOW_AUTO_SAVER_SUGGESTION, confirmationExtras); } + recordBatterySaverEnabledReason(context, reason); } return true; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java index ad022a63eaf6..cb386fbff4ed 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/fuelgauge/BatterySaverUtilsTest.java @@ -16,6 +16,7 @@ package com.android.settingslib.fuelgauge; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_UNKNOWN; import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_NO_SCHEDULE; import static com.android.settingslib.fuelgauge.BatterySaverUtils.KEY_PERCENTAGE; @@ -72,7 +73,8 @@ public class BatterySaverUtilsTest { Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null"); Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isFalse(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true, + SAVER_ENABLED_UNKNOWN)).isFalse(); verify(mMockContext, times(1)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(0)).setPowerSaveModeEnabled(anyBoolean()); @@ -92,7 +94,8 @@ public class BatterySaverUtilsTest { Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1); Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true)); @@ -111,7 +114,8 @@ public class BatterySaverUtilsTest { Secure.putInt(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, 1); Secure.putInt(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, 1); - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, true, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true)); @@ -129,7 +133,8 @@ public class BatterySaverUtilsTest { Secure.putString(mMockResolver, Secure.EXTRA_LOW_POWER_WARNING_ACKNOWLEDGED, "null"); Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, true, false, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(true)); @@ -147,7 +152,8 @@ public class BatterySaverUtilsTest { Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); // When disabling, needFirstTimeWarning doesn't matter. - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, false, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false)); @@ -166,7 +172,8 @@ public class BatterySaverUtilsTest { Secure.putString(mMockResolver, Secure.LOW_POWER_MANUAL_ACTIVATION_COUNT, "null"); // When disabling, needFirstTimeWarning doesn't matter. - assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true)).isTrue(); + assertThat(BatterySaverUtils.setPowerSaveMode(mMockContext, false, true, + SAVER_ENABLED_UNKNOWN)).isTrue(); verify(mMockContext, times(0)).sendBroadcast(any(Intent.class)); verify(mMockPowerManager, times(1)).setPowerSaveModeEnabled(eq(false)); 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/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index fedfb43535cc..78d93bd19a15 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -58,7 +58,6 @@ <uses-permission android:name="android.permission.ACCEPT_HANDOVER" /> <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" /> <uses-permission android:name="android.permission.BODY_SENSORS" /> - <uses-permission android:name="android.permission.BODY_SENSORS_WRIST_TEMPERATURE" /> <uses-permission android:name="com.android.voicemail.permission.ADD_VOICEMAIL" /> <uses-permission android:name="android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS" /> <uses-permission android:name="android.permission.GET_PROCESS_STATE_AND_OOM_SCORE" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index dabb5780621c..a00f401756f7 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -984,7 +984,13 @@ android:exported="true" android:excludeFromRecents="true" android:resizeableActivity="false" - android:theme="@android:style/Theme.NoDisplay" /> + android:theme="@android:style/Theme.NoDisplay" > + + <intent-filter> + <action android:name="com.android.systemui.action.LAUNCH_NOTE_TASK"/> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> <!-- LaunchNoteTaskManagedProfileProxyActivity MUST NOT be exported because it allows caller to specify an Android user when launching the default notes app. --> diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt index 322fc774e805..05630e795476 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt @@ -199,6 +199,9 @@ data class ClockConfig( */ val hasCustomPositionUpdatedAnimation: Boolean = false, + /** Transition to AOD should move smartspace like large clock instead of small clock */ + val useAlternateSmartspaceAODTransition: Boolean = false, + /** True if the clock will react to tone changes in the seed color. */ val isReactiveToTone: Boolean = true, ) 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/res-keyguard/drawable/fp_to_locked.xml b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml new file mode 100644 index 000000000000..61a1cb5f1d31 --- /dev/null +++ b/packages/SystemUI/res-keyguard/drawable/fp_to_locked.xml @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2023 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License + --> +<animated-vector + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:aapt="http://schemas.android.com/aapt"> + <aapt:attr name="android:drawable"> + <vector android:height="65dp" android:width="46dp" android:viewportHeight="65" android:viewportWidth="46"> + <group android:name="_R_G"> + <group android:name="_R_G_L_1_G" android:translateX="3.75" android:translateY="8.25"> + <path android:name="_R_G_L_1_G_D_0_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 "/> + <path android:name="_R_G_L_1_G_D_1_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 "/> + <path android:name="_R_G_L_1_G_D_2_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 "/> + <path android:name="_R_G_L_1_G_D_3_P_0" android:strokeColor="#FF000000" android:strokeLineCap="round" android:strokeLineJoin="round" android:strokeWidth="2" android:strokeAlpha="1" android:pathData=" M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 "/> + </group> + <group android:name="_R_G_L_0_G" android:translateX="20.357" android:translateY="35.75" android:pivotX="2.75" android:pivotY="2.75" android:scaleX="1.41866" android:scaleY="1.41866"> + <path android:name="_R_G_L_0_G_D_0_P_0" android:fillColor="#FF000000" android:fillAlpha="0" android:fillType="nonZero" android:pathData=" M2.75 5.25 C4.13,5.25 5.25,4.13 5.25,2.75 C5.25,1.37 4.13,0.25 2.75,0.25 C1.37,0.25 0.25,1.37 0.25,2.75 C0.25,4.13 1.37,5.25 2.75,5.25c "/> + </group> + </group> + <group android:name="time_group"/> + </vector> + </aapt:attr> + <target android:name="_R_G_L_1_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M27.52 38.98 C26.49,39.95 25.29,40.73 23.98,41.29 C23.17,41.65 22.31,41.91 21.41,42.07 C20.74,42.19 20.05,42.25 19.34,42.25 C18.44,42.25 17.56,42.15 16.72,41.96 C15.93,41.77 15.16,41.51 14.43,41.18 C13.23,40.63 12.13,39.88 11.16,38.98 " android:valueTo="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="143" android:startOffset="107" android:valueFrom="M30.81 32.26 C30.56,32.49 30.27,38.76 29.96,38.9 C29.77,39.46 29.13,39.94 28.57,40.26 C28.15,40.51 26.93,40.65 26.4,40.65 C26.18,40.65 11.91,40.62 11.71,40.58 C10.68,40.53 9.06,39.79 8.89,38.88 C8.6,38.74 8.34,32.48 8.1,32.27 " android:valueTo="M30.64 30.14 C30.64,30.14 30.64,38.14 30.64,38.14 C30.64,38.77 30.36,39.32 29.91,39.69 C29.57,39.97 29.12,40.14 28.64,40.14 C28.64,40.14 10.14,40.14 10.14,40.14 C9.04,40.14 8.14,39.25 8.14,38.14 C8.14,38.14 8.14,30.14 8.14,30.14 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.331,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="strokeAlpha" android:duration="140" android:startOffset="0" android:valueFrom="1" android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="strokeAlpha" android:duration="50" android:startOffset="140" android:valueFrom="1" android:valueTo="0" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_1_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="0" android:valueFrom="M8.64 34.07 C7.89,31.97 7.89,29.85 7.89,29.85 C7.89,24.05 12.81,19.34 19.34,19.34 C25.87,19.34 30.8,24.05 30.8,29.85 C30.8,29.85 30.8,30.16 30.8,30.16 C30.8,32.32 29.04,34.07 26.89,34.07 C25.28,34.07 23.86,33.1 23.27,31.61 C23.27,31.61 21.96,28.34 21.96,28.34 C21.37,26.85 19.93,25.89 18.34,25.89 C16.18,25.89 14.43,27.64 14.43,29.8 C14.43,31.42 14.87,32.99 15.68,34.36 C16.22,35.26 16.93,36.08 17.77,36.75 C17.77,36.75 18.52,37.34 18.52,37.34 " android:valueTo="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="107" android:startOffset="107" android:valueFrom="M18.93 32.18 C17.11,32.68 16.62,30.26 16.62,30.26 C16.62,28.78 17.81,27.59 19.39,27.59 C20.96,27.59 22.15,28.78 22.15,30.26 C22.15,30.26 22.15,30.34 22.15,30.34 C22.15,30.89 21.11,32.54 19.57,32.19 C19.19,32.1 20.48,31.09 20.34,30.71 C20.34,30.71 20.02,29.88 20.02,29.88 C19.88,29.5 19.53,29.25 19.15,29.25 C18.63,29.25 18,29.67 18,30.22 C18,30.57 18.06,31.08 18.32,31.51 C18.49,31.8 19.02,32.25 19.79,32.04 C20.41,31.7 20.38,31.36 20.38,31.36 " android:valueTo="M19.42 31.53 C18.15,31.52 18.11,30.33 18.11,30.33 C18.11,29.59 18.66,28.98 19.4,28.98 C20.13,28.98 20.69,29.59 20.69,30.33 C20.69,30.33 20.69,30.37 20.69,30.37 C20.69,30.64 20.49,30.87 20.25,30.87 C20.07,30.87 19.91,30.74 19.84,30.55 C19.84,30.55 19.69,30.14 19.69,30.14 C19.63,29.94 19.46,29.82 19.28,29.82 C19.04,29.82 18.61,30.02 18.61,30.29 C18.61,30.43 18.6,30.75 18.76,31.03 C18.87,31.21 19.21,31.77 19.96,31.41 C20.69,31.01 20.69,30.34 20.69,30.34 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_2_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="250" android:startOffset="0" android:valueFrom="M6.25 19.34 C7.48,17.3 9.46,15.58 11.9,14.42 C12.93,13.94 14.03,13.55 15.2,13.27 C16.51,12.96 17.9,12.8 19.34,12.8 C20.77,12.8 22.14,12.96 23.45,13.26 C24.9,13.6 26.26,14.12 27.48,14.78 C29.6,15.92 31.32,17.5 32.43,19.34 " android:valueTo="M8.14 30.22 C8.14,30.22 8.14,22.22 8.14,22.22 C8.14,21.71 8.33,21.25 8.64,20.9 C9,20.48 9.54,20.22 10.14,20.22 C10.14,20.22 28.64,20.22 28.64,20.22 C29.75,20.22 30.64,21.11 30.64,22.22 C30.64,22.22 30.64,30.14 30.64,30.14 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.189,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_1_G_D_3_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="pathData" android:duration="95" android:startOffset="0" android:valueFrom="M9.52 8.7 C10.98,7.91 12.58,7.28 14.28,6.86 C15.89,6.46 17.58,6.25 19.34,6.25 C21.06,6.25 22.72,6.45 24.3,6.83 C26.04,7.25 27.67,7.89 29.16,8.7 " android:valueTo="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.541,0 0.833,0.767 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="24" android:startOffset="95" android:valueFrom="M11.47 14.84 C11.47,14.84 12.21,11.43 13.54,9.84 C14.84,8.28 16.68,7.22 19.35,7.22 C22.01,7.22 23.98,8.4 25.19,10.18 C26.39,11.96 27.25,14.84 27.25,14.84 " android:valueTo="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.833,0.767 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="pathData" android:duration="81" android:startOffset="119" android:valueFrom="M12.11 16.85 C12.11,16.85 12.82,12.71 13.37,11.5 C14.17,9.24 16.38,7.53 19.35,7.53 C22.32,7.53 24.61,9.32 25.35,11.72 C25.61,12.64 26.62,16.85 26.62,16.85 " android:valueTo="M13.12 20.04 C13.12,20.04 13.11,14.15 13.11,14.15 C13.11,10.77 15.91,8.04 19.36,8.04 C22.81,8.04 25.61,10.77 25.61,14.15 C25.61,14.15 25.62,20.04 25.62,20.04 " android:valueType="pathType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.233 0.261,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G_D_0_P_0"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="fillAlpha" android:duration="120" android:startOffset="0" android:valueFrom="0" android:valueTo="0" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="fillAlpha" android:duration="20" android:startOffset="120" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="_R_G_L_0_G"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="scaleX" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleY" android:duration="120" android:startOffset="0" android:valueFrom="1.4186600000000003" android:valueTo="1.4186600000000003" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleX" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + <objectAnimator android:propertyName="scaleY" android:duration="130" android:startOffset="120" android:valueFrom="1.4186600000000003" android:valueTo="1" android:valueType="floatType"> + <aapt:attr name="android:interpolator"> + <pathInterpolator android:pathData="M 0.0,0.0 c0.001,0 0.43,1 1.0,1.0"/> + </aapt:attr> + </objectAnimator> + </set> + </aapt:attr> + </target> + <target android:name="time_group"> + <aapt:attr name="android:animation"> + <set android:ordering="together"> + <objectAnimator android:propertyName="translateX" android:duration="517" android:startOffset="0" android:valueFrom="0" android:valueTo="1" android:valueType="floatType"/> + </set> + </aapt:attr> + </target> +</animated-vector> diff --git a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml index 951d6fed0a17..f3325ecefada 100644 --- a/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml +++ b/packages/SystemUI/res-keyguard/drawable/super_lock_icon.xml @@ -104,4 +104,9 @@ android:fromId="@id/unlocked" android:toId="@id/locked" android:drawable="@drawable/unlocked_to_locked" /> + + <transition + android:fromId="@id/locked_fp" + android:toId="@id/locked" + android:drawable="@drawable/fp_to_locked" /> </animated-selector> diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml index 2143fc4db852..edd3047203b3 100644 --- a/packages/SystemUI/res-keyguard/values/strings.xml +++ b/packages/SystemUI/res-keyguard/values/strings.xml @@ -21,19 +21,19 @@ <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=30] --> <string name="keyguard_enter_your_pin">Enter your PIN</string> - <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=26] --> + <!-- Instructions telling the user to enter their PIN password to unlock the keyguard [CHAR LIMIT=48] --> <string name="keyguard_enter_pin">Enter PIN</string> <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=30] --> <string name="keyguard_enter_your_pattern">Enter your pattern</string> - <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=26] --> + <!-- Instructions telling the user to enter their pattern to unlock the keyguard [CHAR LIMIT=48] --> <string name="keyguard_enter_pattern">Draw pattern</string> <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=30] --> <string name="keyguard_enter_your_password">Enter your password</string> - <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=26] --> + <!-- Instructions telling the user to enter their text password to unlock the keyguard [CHAR LIMIT=48] --> <string name="keyguard_enter_password">Enter password</string> <!-- Shown in the lock screen when there is SIM card IO error. --> @@ -128,103 +128,103 @@ <!-- Message shown when user enters wrong pattern --> <string name="kg_wrong_pattern">Wrong pattern</string> - <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] --> + <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] --> <string name="kg_wrong_pattern_try_again">Wrong pattern. Try again.</string> <!-- Message shown when user enters wrong password --> <string name="kg_wrong_password">Wrong password</string> - <!-- Message shown when user enters wrong pattern [CHAR LIMIT=26] --> + <!-- Message shown when user enters wrong pattern [CHAR LIMIT=48] --> <string name="kg_wrong_password_try_again">Wrong password. Try again.</string> <!-- Message shown when user enters wrong PIN --> <string name="kg_wrong_pin">Wrong PIN</string> - <!-- Message shown when user enters wrong PIN [CHAR LIMIT=26] --> + <!-- Message shown when user enters wrong PIN [CHAR LIMIT=48] --> <string name="kg_wrong_pin_try_again">Wrong PIN. Try again.</string> - <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2. [CHAR LIMIT=52] --> + <!-- Message shown when user enters wrong PIN/password/pattern below the main message, for ex: "Wrong PIN. Try again" in line 1 and the following text in line 2. [CHAR LIMIT=70] --> <string name="kg_wrong_input_try_fp_suggestion">Or unlock with fingerprint</string> - <!-- Message shown when user fingerprint is not recognized [CHAR LIMIT=26] --> + <!-- Message shown when user fingerprint is not recognized [CHAR LIMIT=48] --> <string name="kg_fp_not_recognized">Fingerprint not recognized</string> - <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=26] --> + <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=48] --> <string name="bouncer_face_not_recognized">Face not recognized</string> - <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] --> + <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] --> <string name="kg_bio_try_again_or_pin">Try again or enter PIN</string> - <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] --> + <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] --> <string name="kg_bio_try_again_or_password">Try again or enter password</string> - <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=52] --> + <!-- Message shown when we want the users to try biometric auth again or use pin/pattern/password [CHAR LIMIT=70] --> <string name="kg_bio_try_again_or_pattern">Try again or draw pattern</string> - <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] --> + <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] --> <string name="kg_bio_too_many_attempts_pin">PIN is required after too many attempts</string> - <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] --> + <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] --> <string name="kg_bio_too_many_attempts_password">Password is required after too many attempts</string> - <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=52] --> + <!-- Message shown when we are on bouncer after temporary lockout of either face or fingerprint [CHAR LIMIT=70] --> <string name="kg_bio_too_many_attempts_pattern">Pattern is required after too many attempts</string> - <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] --> + <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] --> <string name="kg_unlock_with_pin_or_fp">Unlock with PIN or fingerprint</string> - <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] --> + <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] --> <string name="kg_unlock_with_password_or_fp">Unlock with password or fingerprint</string> - <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=26] --> + <!-- Instructions when the user can unlock with PIN/password/pattern or fingerprint from bouncer. [CHAR LIMIT=48] --> <string name="kg_unlock_with_pattern_or_fp">Unlock with pattern or fingerprint</string> - <!-- Message shown when we are on bouncer after Device admin requested lockdown. [CHAR LIMIT=52] --> + <!-- Message shown when we are on bouncer after Device admin requested lockdown. [CHAR LIMIT=70] --> <string name="kg_prompt_after_dpm_lock">For added security, device was locked by work policy</string> - <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] --> + <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] --> <string name="kg_prompt_after_user_lockdown_pin">PIN is required after lockdown</string> - <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] --> + <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] --> <string name="kg_prompt_after_user_lockdown_password">Password is required after lockdown</string> - <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=52] --> + <!-- Message shown for pin/pattern/password when we are on bouncer after user triggered lockdown. [CHAR LIMIT=70] --> <string name="kg_prompt_after_user_lockdown_pattern">Pattern is required after lockdown</string> - <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update. [CHAR LIMIT=52] --> + <!-- Message shown to prepare for an unattended update (OTA). Also known as an over-the-air (OTA) update. [CHAR LIMIT=70] --> <string name="kg_prompt_unattended_update">Update will install during inactive hours</string> - <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] --> + <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=70] --> <string name="kg_prompt_pin_auth_timeout">Added security required. PIN not used for a while.</string> - <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] --> + <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=70] --> <string name="kg_prompt_password_auth_timeout">Added security required. Password not used for a while.</string> - <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=52] --> + <!-- Message shown when primary authentication hasn't been used for some time. [CHAR LIMIT=76] --> <string name="kg_prompt_pattern_auth_timeout">Added security required. Pattern not used for a while.</string> - <!-- Message shown when device hasn't been unlocked for a while. [CHAR LIMIT=52] --> + <!-- Message shown when device hasn't been unlocked for a while. [CHAR LIMIT=82] --> <string name="kg_prompt_auth_timeout">Added security required. Device wasn\u2019t unlocked for a while.</string> - <!-- Message shown when face unlock is not available after too many failed face authentication attempts. [CHAR LIMIT=52] --> + <!-- Message shown when face unlock is not available after too many failed face authentication attempts. [CHAR LIMIT=70] --> <string name="kg_face_locked_out">Can\u2019t unlock with face. Too many attempts.</string> - <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts. [CHAR LIMIT=52] --> + <!-- Message shown when fingerprint unlock isn't available after too many failed fingerprint authentication attempts. [CHAR LIMIT=75] --> <string name="kg_fp_locked_out">Can\u2019t unlock with fingerprint. Too many attempts.</string> - <!-- Message shown when Trust Agent is disabled. [CHAR LIMIT=52] --> + <!-- Message shown when Trust Agent is disabled. [CHAR LIMIT=70] --> <string name="kg_trust_agent_disabled">Trust agent is unavailable</string> - <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] --> + <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] --> <string name="kg_primary_auth_locked_out_pin">Too many attempts with incorrect PIN</string> - <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] --> + <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] --> <string name="kg_primary_auth_locked_out_pattern">Too many attempts with incorrect pattern</string> - <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=52] --> + <!-- Message shown when primary auth is locked out after too many attempts [CHAR LIMIT=70] --> <string name="kg_primary_auth_locked_out_password">Too many attempts with incorrect password</string> - <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=26]--> + <!-- Countdown message shown after too many failed unlock attempts [CHAR LIMIT=48]--> <string name="kg_too_many_failed_attempts_countdown">{count, plural, =1 {Try again in # second.} other {Try again in # seconds.} @@ -296,13 +296,13 @@ <!-- Description of airplane mode --> <string name="airplane_mode">Airplane mode</string> - <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=52] --> + <!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=70] --> <string name="kg_prompt_reason_restart_pattern">Pattern is required after device restarts</string> - <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=52] --> + <!-- An explanation text that the pin needs to be entered since the device has just been restarted. [CHAR LIMIT=70] --> <string name="kg_prompt_reason_restart_pin">PIN is required after device restarts</string> - <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=52] --> + <!-- An explanation text that the password needs to be entered since the device has just been restarted. [CHAR LIMIT=70] --> <string name="kg_prompt_reason_restart_password">Password is required after device restarts</string> <!-- An explanation text that the pattern needs to be solved since the user hasn't used strong authentication since quite some time. [CHAR LIMIT=80] --> diff --git a/packages/SystemUI/res/drawable/chipbar_background.xml b/packages/SystemUI/res/drawable/chipbar_background.xml index 57221776a32f..7530f5ba244a 100644 --- a/packages/SystemUI/res/drawable/chipbar_background.xml +++ b/packages/SystemUI/res/drawable/chipbar_background.xml @@ -17,6 +17,6 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> - <solid android:color="?androidprv:attr/colorAccentSecondary" /> + <solid android:color="?androidprv:attr/materialColorSecondaryFixed" /> <corners android:radius="32dp" /> </shape> diff --git a/packages/SystemUI/res/drawable/chipbar_end_button_background.xml b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml index 80c7207a35b6..a3832eef957f 100644 --- a/packages/SystemUI/res/drawable/chipbar_end_button_background.xml +++ b/packages/SystemUI/res/drawable/chipbar_end_button_background.xml @@ -20,7 +20,7 @@ android:color="?android:textColorPrimary"> <item android:id="@android:id/background"> <shape> - <solid android:color="@android:color/system_accent1_200"/> + <solid android:color="?androidprv:attr/materialColorPrimaryFixedDim"/> <corners android:radius="24dp" /> </shape> </item> diff --git a/packages/SystemUI/res/drawable/hearing.xml b/packages/SystemUI/res/drawable/hearing.xml new file mode 100644 index 000000000000..02f5f92ec5fe --- /dev/null +++ b/packages/SystemUI/res/drawable/hearing.xml @@ -0,0 +1,24 @@ +<!-- Copyright (C) 2023 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportWidth="48" + android:viewportHeight="48" + android:tint="?attr/colorControlNormal"> + <path + android:fillColor="@android:color/white" + android:pathData="M34.4,43.95Q31.55,43.95 29.45,42.4Q27.35,40.85 26.35,38.3Q25.35,35.75 24.375,34.325Q23.4,32.9 20.7,30.75Q17.4,28.1 15.95,25.1Q14.5,22.1 14.5,17.8Q14.5,11.8 18.3,7.975Q22.1,4.15 28.1,4.15Q34,4.15 37.875,7.825Q41.75,11.5 42,17.2H39Q38.75,12.8 35.725,9.975Q32.7,7.15 28.1,7.15Q23.6,7.15 20.55,10.225Q17.5,13.3 17.5,17.8Q17.5,21.4 18.9,24.025Q20.3,26.65 23.55,29.1Q25.5,30.55 26.675,32.25Q27.85,33.95 28.9,36.45Q29.75,38.55 31.125,39.75Q32.5,40.95 34.4,40.95Q36.15,40.95 37.425,39.75Q38.7,38.55 38.95,36.8H41.95Q41.7,39.8 39.55,41.875Q37.4,43.95 34.4,43.95ZM11.95,32.9Q9.1,29.75 7.55,25.825Q6,21.9 6,17.6Q6,13.35 7.475,9.375Q8.95,5.4 11.95,2.35L14.2,4.35Q11.6,7 10.3,10.425Q9,13.85 9,17.6Q9,21.3 10.325,24.725Q11.65,28.15 14.2,30.85ZM28.1,22.45Q26.15,22.45 24.8,21.1Q23.45,19.75 23.45,17.8Q23.45,15.85 24.8,14.45Q26.15,13.05 28.1,13.05Q30.05,13.05 31.45,14.45Q32.85,15.85 32.85,17.8Q32.85,19.75 31.45,21.1Q30.05,22.45 28.1,22.45Z"/> +</vector> diff --git a/packages/SystemUI/res/layout/chipbar.xml b/packages/SystemUI/res/layout/chipbar.xml index 762dcdced9c4..8fa975be43d2 100644 --- a/packages/SystemUI/res/layout/chipbar.xml +++ b/packages/SystemUI/res/layout/chipbar.xml @@ -49,15 +49,17 @@ android:alpha="0.0" /> + <!-- LINT.IfChange textColor --> <TextView android:id="@+id/text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:textSize="@dimen/chipbar_text_size" - android:textColor="@color/chipbar_text_and_icon_color" + style="@style/Chipbar.Text" + android:textColor="?androidprv:attr/materialColorOnSecondaryFixed" android:alpha="0.0" /> + <!-- LINT.ThenChange(systemui.temporarydisplay.chipbar.ChipbarInfo.kt) --> <!-- At most one of [loading, failure_icon, undo] will be visible at a time. --> <ImageView @@ -66,7 +68,7 @@ android:layout_height="@dimen/chipbar_end_icon_size" android:layout_marginStart="@dimen/chipbar_end_item_start_margin" android:src="@drawable/ic_progress_activity" - android:tint="@android:color/system_accent2_700" + android:tint="?androidprv:attr/materialColorOnSecondaryFixedVariant" android:alpha="0.0" /> @@ -84,9 +86,9 @@ android:id="@+id/end_button" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:textColor="?androidprv:attr/textColorOnAccent" android:layout_marginStart="@dimen/chipbar_end_item_start_margin" - android:textSize="@dimen/chipbar_text_size" + style="@style/Chipbar.Text" + android:textColor="?androidprv:attr/materialColorOnPrimaryFixed" android:paddingStart="@dimen/chipbar_outer_padding" android:paddingEnd="@dimen/chipbar_outer_padding" android:paddingTop="@dimen/chipbar_end_button_vertical_padding" diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index f1fca7603571..d7106762d17b 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -152,10 +152,4 @@ <include layout="@layout/keyguard_bottom_area" android:visibility="gone" /> - - <FrameLayout - android:id="@+id/preview_container" - android:layout_width="match_parent" - android:layout_height="match_parent"> - </FrameLayout> </com.android.systemui.shade.NotificationPanelView> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index 96e6d4e1a234..cb8c2a73c15d 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -231,9 +231,6 @@ <color name="people_tile_background">@color/material_dynamic_secondary95</color> - <!-- Chipbar --> - <color name="chipbar_text_and_icon_color">@android:color/system_accent2_900</color> - <!-- Internet Dialog --> <!-- Material next state on color--> <color name="settingslib_state_on_color">@color/settingslib_state_on</color> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4db42aacbdcd..0eb0a078a478 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1110,6 +1110,7 @@ <!-- Chipbar --> <!-- (Used for media tap-to-transfer chip for sender device and active unlock) --> <dimen name="chipbar_outer_padding">16dp</dimen> + <dimen name="chipbar_outer_padding_half">8dp</dimen> <dimen name="chipbar_text_size">16sp</dimen> <dimen name="chipbar_start_icon_size">24dp</dimen> <dimen name="chipbar_end_icon_size">20dp</dimen> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 2098aea87ab2..a359d3b21938 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -70,6 +70,15 @@ <item name="android:fontWeight">700</item> </style> + <style name="Chipbar" /> + + <style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title"> + <!-- Text size should be kept in sync with the notification conversation header size. (The + conversation header doesn't have a defined style, so the size must be copied here.) + See notification_template_conversation_header.xml. --> + <item name="android:textSize">16sp</item> + </style> + <style name="TextAppearance" /> <style name="TextAppearance.QS"> diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt index 0779653430b2..7262a736c433 100644 --- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt +++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt @@ -25,6 +25,7 @@ import android.graphics.Rect import android.text.format.DateFormat import android.util.TypedValue import android.view.View +import android.view.View.OnAttachStateChangeListener import android.view.ViewTreeObserver import android.widget.FrameLayout import androidx.annotation.VisibleForTesting @@ -105,6 +106,24 @@ constructor( } updateFontSizes() updateTimeListeners() + value.smallClock.view.addOnAttachStateChangeListener( + object : OnAttachStateChangeListener { + override fun onViewAttachedToWindow(p0: View?) { + value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } + + override fun onViewDetachedFromWindow(p0: View?) { + } + }) + value.largeClock.view.addOnAttachStateChangeListener( + object : OnAttachStateChangeListener { + override fun onViewAttachedToWindow(p0: View?) { + value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context)) + } + + override fun onViewDetachedFromWindow(p0: View?) { + } + }) } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 9290220b8698..25d17928351a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -109,7 +109,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final ContentObserver mShowWeatherObserver = new ContentObserver(null) { @Override public void onChange(boolean change) { - setDateWeatherVisibility(); + setWeatherVisibility(); } }; @@ -236,6 +236,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS updateDoubleLineClock(); setDateWeatherVisibility(); + setWeatherVisibility(); mKeyguardUnlockAnimationController.addKeyguardUnlockAnimationListener( mKeyguardUnlockAnimationListener); @@ -266,6 +267,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mStatusArea.removeView(mDateWeatherView); addDateWeatherView(index); } + setDateWeatherVisibility(); + setWeatherVisibility(); } int index = mStatusArea.indexOfChild(mSmartspaceView); if (index >= 0) { @@ -487,16 +490,19 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS } private void setDateWeatherVisibility() { - if (mDateWeatherView != null || mWeatherView != null) { + if (mDateWeatherView != null) { mUiExecutor.execute(() -> { - if (mDateWeatherView != null) { - mDateWeatherView.setVisibility( - clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE); - } - if (mWeatherView != null) { - mWeatherView.setVisibility( - mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE); - } + mDateWeatherView.setVisibility( + clockHasCustomWeatherDataDisplay() ? View.GONE : View.VISIBLE); + }); + } + } + + private void setWeatherVisibility() { + if (mWeatherView != null) { + mUiExecutor.execute(() -> { + mWeatherView.setVisibility( + mSmartspaceController.isWeatherEnabled() ? View.VISIBLE : View.GONE); }); } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 87a775866faf..25cda9828d87 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -75,6 +75,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 +116,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,6 +302,7 @@ 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(); @@ -389,7 +392,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 +418,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mTelephonyManager = telephonyManager; mViewMediatorCallback = viewMediatorCallback; mAudioManager = audioManager; + mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor; } @Override @@ -526,7 +531,6 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) { if (mCancelAction != null) { mCancelAction.run(); - mCancelAction = null; } mDismissAction = action; mCancelAction = cancelAction; 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/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java index 6f549881d12a..0cdef4d63639 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java @@ -73,7 +73,7 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV */ private static final int KEYGUARD_STATUS_VIEW_CUSTOM_CLOCK_MOVE_DURATION = 1000; - private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = + public static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD); private final KeyguardSliceViewController mKeyguardSliceViewController; @@ -221,16 +221,32 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV mView.setImportantForAccessibility(mode); } + @VisibleForTesting + void setProperty(AnimatableProperty property, float value, boolean animate) { + PropertyAnimator.setProperty(mView, property, value, CLOCK_ANIMATION_PROPERTIES, animate); + } + /** * Update position of the view with an optional animation */ public void updatePosition(int x, int y, float scale, boolean animate) { float oldY = mView.getY(); - PropertyAnimator.setProperty(mView, AnimatableProperty.Y, y, CLOCK_ANIMATION_PROPERTIES, - animate); + setProperty(AnimatableProperty.Y, y, animate); + + ClockController clock = mKeyguardClockSwitchController.getClock(); + if (clock != null && clock.getConfig().getUseAlternateSmartspaceAODTransition()) { + // If requested, scale the entire view instead of just the clock view + mKeyguardClockSwitchController.updatePosition(x, 1f /* scale */, + CLOCK_ANIMATION_PROPERTIES, animate); + setProperty(AnimatableProperty.SCALE_X, scale, animate); + setProperty(AnimatableProperty.SCALE_Y, scale, animate); + } else { + mKeyguardClockSwitchController.updatePosition(x, scale, + CLOCK_ANIMATION_PROPERTIES, animate); + setProperty(AnimatableProperty.SCALE_X, 1f, animate); + setProperty(AnimatableProperty.SCALE_Y, 1f, animate); + } - mKeyguardClockSwitchController.updatePosition(x, scale, CLOCK_ANIMATION_PROPERTIES, - animate); if (oldY != y) { mKeyguardClockSwitchController.updateKeyguardStatusViewOffset(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 350c4ed084b9..33a822440866 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -154,6 +154,15 @@ 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.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 +381,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 @@ -1165,8 +1176,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 +1204,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 +1217,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 +1242,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mLogger.logFaceAuthForWrongUser(authUserId); return; } - if (isFaceDisabled(userId)) { + if (!isFaceAuthInteractorEnabled() && isFaceDisabled(userId)) { mLogger.logFaceAuthDisabledForUser(userId); return; } @@ -1215,6 +1254,10 @@ 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) { Assert.isMainThread(); mLogger.logFaceAuthHelpMsg(msgId, helpString); @@ -1226,22 +1269,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 +1330,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 +1396,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 +1464,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 +1479,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 +1730,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 +1946,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 +2715,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 +2732,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 +3244,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } public boolean isFaceLockedOut() { + if (isFaceAuthInteractorEnabled()) { + return getFaceAuthInteractor().isLockedOut(); + } return mFaceLockedOutPermanent; } @@ -3202,13 +3295,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 +3335,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 +4202,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 +4234,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()); 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/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 235a8bca6d1e..5f2afe8f755d 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -294,6 +294,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme final CharSequence prevContentDescription = mView.getContentDescription(); if (mShowLockIcon) { + if (wasShowingFpIcon) { + // fp icon was shown by UdfpsView, and now we still want to animate the transition + // in this drawable + mView.updateIcon(ICON_FINGERPRINT, false); + } mView.updateIcon(ICON_LOCK, false); mView.setContentDescription(mLockedLabel); mView.setVisibility(View.VISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt index d15a2afa0d4a..6c62a3936910 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetector.kt @@ -39,7 +39,7 @@ constructor( private fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) = mainExecutor.execute { action?.let { - if (event.tracking || event.expanded) { + if (event.tracking || (event.expanded && event.fraction > 0)) { Log.v(TAG, "Detected panel interaction, event: $event") it.onPanelInteraction.run() disable() 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/common/shared/model/TintedIcon.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt index 6a6c3eb05399..0869351e727b 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/TintedIcon.kt @@ -16,10 +16,10 @@ package com.android.systemui.common.shared.model -import androidx.annotation.ColorRes +import androidx.annotation.AttrRes /** Models an icon with a specific tint. */ data class TintedIcon( val icon: Icon, - @ColorRes val tint: Int?, + @AttrRes val tint: Int?, ) diff --git a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt index bcc5932dcf30..5c5723fa0a5e 100644 --- a/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/common/ui/binder/TintedIconViewBinder.kt @@ -17,6 +17,7 @@ package com.android.systemui.common.ui.binder import android.widget.ImageView +import com.android.settingslib.Utils import com.android.systemui.common.shared.model.TintedIcon object TintedIconViewBinder { @@ -33,7 +34,7 @@ object TintedIconViewBinder { IconViewBinder.bind(tintedIcon.icon, view) view.imageTintList = if (tintedIcon.tint != null) { - view.resources.getColorStateList(tintedIcon.tint, view.context.theme) + Utils.getColorAttr(view.context, tintedIcon.tint) } else { null } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java index 7f44463f1191..aca621bd9e55 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java @@ -41,6 +41,7 @@ import com.google.common.util.concurrent.ListenableFuture; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -195,7 +196,14 @@ public class DreamOverlayTouchMonitor { * Called by the monitor when this session is removed. */ private void onRemoved() { - mCallbacks.forEach(callback -> callback.onRemoved()); + mEventListeners.clear(); + mGestureListeners.clear(); + final Iterator<Callback> iter = mCallbacks.iterator(); + while (iter.hasNext()) { + final Callback callback = iter.next(); + callback.onRemoved(); + iter.remove(); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 69d758213f1e..ffacf01347e0 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -108,11 +108,6 @@ object Flags { val NOTIFICATION_SHELF_REFACTOR = unreleasedFlag(271161129, "notification_shelf_refactor") - // TODO(b/263414400): Tracking Bug - @JvmField - val NOTIFICATION_ANIMATE_BIG_PICTURE = - releasedFlag(120, "notification_animate_big_picture") - @JvmField val ANIMATED_NOTIFICATION_SHADE_INSETS = releasedFlag(270682168, "animated_notification_shade_insets") @@ -419,9 +414,6 @@ object Flags { // TODO(b/254512758): Tracking Bug @JvmField val ROUNDED_BOX_RIPPLE = releasedFlag(1002, "rounded_box_ripple") - // TODO(b/270882464): Tracking Bug - val ENABLE_DOCK_SETUP_V2 = releasedFlag(1005, "enable_dock_setup_v2") - // TODO(b/265045965): Tracking Bug val SHOW_LOWLIGHT_ON_DIRECT_BOOT = releasedFlag(1003, "show_lowlight_on_direct_boot") @@ -608,9 +600,6 @@ object Flags { // TODO(b/254512507): Tracking Bug val CHOOSER_UNBUNDLED = releasedFlag(1500, "chooser_unbundled") - // TODO(b/266983432) Tracking Bug - val SHARESHEET_CUSTOM_ACTIONS = releasedFlag(1501, "sharesheet_custom_actions") - // TODO(b/266982749) Tracking Bug val SHARESHEET_RESELECTION_ACTION = releasedFlag(1502, "sharesheet_reselection_action") @@ -704,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/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 416b2379eef8..e24d0ed17e29 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -53,7 +53,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.pm.UserInfo; import android.graphics.Matrix; import android.hardware.biometrics.BiometricSourceType; import android.media.AudioAttributes; @@ -560,17 +559,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onUserSwitching(int userId) { if (DEBUG) Log.d(TAG, String.format("onUserSwitching %d", userId)); - // Note that the mLockPatternUtils user has already been updated from setCurrentUser. - // We need to force a reset of the views, since lockNow (called by - // ActivityManagerService) will not reconstruct the keyguard if it is already showing. synchronized (KeyguardViewMediator.this) { resetKeyguardDonePendingLocked(); - if (mLockPatternUtils.isLockScreenDisabled(userId)) { - // If we are switching to a user that has keyguard disabled, dismiss keyguard. - dismiss(null /* callback */, null /* message */); - } else { - resetStateLocked(); - } + dismiss(null /* callback */, null /* message */); adjustStatusBarLocked(); } } @@ -578,16 +569,9 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, @Override public void onUserSwitchComplete(int userId) { if (DEBUG) Log.d(TAG, String.format("onUserSwitchComplete %d", userId)); - if (userId != UserHandle.USER_SYSTEM) { - UserInfo info = UserManager.get(mContext).getUserInfo(userId); - // Don't try to dismiss if the user has Pin/Pattern/Password set - if (info == null || mLockPatternUtils.isSecure(userId)) { - return; - } else if (info.isGuest() || info.isDemo()) { - // If we just switched to a guest, try to dismiss keyguard. - dismiss(null /* callback */, null /* message */); - } - } + // We are calling dismiss again and with a delay as there are race conditions + // in some scenarios caused by async layout listeners + new Handler().postDelayed(() -> dismiss(null /* callback */, null /* message */), 500); } @Override @@ -2213,58 +2197,72 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) { @Override public void handleMessage(Message msg) { + String message = ""; switch (msg.what) { case SHOW: + message = "SHOW"; handleShow((Bundle) msg.obj); break; case HIDE: + message = "HIDE"; handleHide(); break; case RESET: + message = "RESET"; handleReset(); break; case VERIFY_UNLOCK: + message = "VERIFY_UNLOCK"; Trace.beginSection("KeyguardViewMediator#handleMessage VERIFY_UNLOCK"); handleVerifyUnlock(); Trace.endSection(); break; case NOTIFY_STARTED_GOING_TO_SLEEP: + message = "NOTIFY_STARTED_GOING_TO_SLEEP"; handleNotifyStartedGoingToSleep(); break; case NOTIFY_FINISHED_GOING_TO_SLEEP: + message = "NOTIFY_FINISHED_GOING_TO_SLEEP"; handleNotifyFinishedGoingToSleep(); break; case NOTIFY_STARTED_WAKING_UP: + message = "NOTIFY_STARTED_WAKING_UP"; Trace.beginSection( "KeyguardViewMediator#handleMessage NOTIFY_STARTED_WAKING_UP"); handleNotifyStartedWakingUp(); Trace.endSection(); break; case KEYGUARD_DONE: + message = "KEYGUARD_DONE"; Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE"); handleKeyguardDone(); Trace.endSection(); break; case KEYGUARD_DONE_DRAWING: + message = "KEYGUARD_DONE_DRAWING"; Trace.beginSection("KeyguardViewMediator#handleMessage KEYGUARD_DONE_DRAWING"); handleKeyguardDoneDrawing(); Trace.endSection(); break; case SET_OCCLUDED: + message = "SET_OCCLUDED"; Trace.beginSection("KeyguardViewMediator#handleMessage SET_OCCLUDED"); handleSetOccluded(msg.arg1 != 0, msg.arg2 != 0); Trace.endSection(); break; case KEYGUARD_TIMEOUT: + message = "KEYGUARD_TIMEOUT"; synchronized (KeyguardViewMediator.this) { doKeyguardLocked((Bundle) msg.obj); } break; case DISMISS: - final DismissMessage message = (DismissMessage) msg.obj; - handleDismiss(message.getCallback(), message.getMessage()); + message = "DISMISS"; + final DismissMessage dismissMsg = (DismissMessage) msg.obj; + handleDismiss(dismissMsg.getCallback(), dismissMsg.getMessage()); break; case START_KEYGUARD_EXIT_ANIM: + message = "START_KEYGUARD_EXIT_ANIM"; Trace.beginSection( "KeyguardViewMediator#handleMessage START_KEYGUARD_EXIT_ANIM"); synchronized (KeyguardViewMediator.this) { @@ -2282,21 +2280,25 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, Trace.endSection(); break; case CANCEL_KEYGUARD_EXIT_ANIM: + message = "CANCEL_KEYGUARD_EXIT_ANIM"; Trace.beginSection( "KeyguardViewMediator#handleMessage CANCEL_KEYGUARD_EXIT_ANIM"); handleCancelKeyguardExitAnimation(); Trace.endSection(); break; case KEYGUARD_DONE_PENDING_TIMEOUT: + message = "KEYGUARD_DONE_PENDING_TIMEOUT"; Trace.beginSection("KeyguardViewMediator#handleMessage" + " KEYGUARD_DONE_PENDING_TIMEOUT"); Log.w(TAG, "Timeout while waiting for activity drawn!"); Trace.endSection(); break; case SYSTEM_READY: + message = "SYSTEM_READY"; handleSystemReady(); break; } + Log.d(TAG, "KeyguardViewMediator queue processing message: " + message); } }; 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 56e73980079d..c4fc8834df83 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 @@ -44,6 +44,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 +80,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 +89,13 @@ 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> /** * Trigger face authentication. @@ -126,6 +131,8 @@ constructor( private val keyguardRepository: KeyguardRepository, private val keyguardInteractor: KeyguardInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, + @FaceDetectTableLog private val faceDetectLog: TableLogBuffer, + @FaceAuthTableLog private val faceAuthLog: TableLogBuffer, dumpManager: DumpManager, ) : DeviceEntryFaceAuthRepository, Dumpable { private var authCancellationSignal: CancellationSignal? = null @@ -166,7 +173,7 @@ constructor( override val isAuthenticated: Flow<Boolean> get() = _isAuthenticated - private val bypassEnabled: Flow<Boolean> = + override val isBypassEnabled: Flow<Boolean> = keyguardBypassController?.let { conflatedCallbackFlow { val callback = @@ -221,17 +228,19 @@ constructor( // Face detection can run only when lockscreen bypass is enabled // & detection is supported & biometric unlock is not allowed. listOf( - canFaceAuthOrDetectRun(), - logAndObserve(bypassEnabled, "bypassEnabled"), + 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) @@ -243,6 +252,7 @@ constructor( cancelDetection() } } + .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false) .launchIn(applicationScope) } @@ -251,26 +261,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( @@ -279,15 +297,18 @@ constructor( ) { a, b -> !a || b }, - "secureCameraNotActiveOrAltBouncerIsShowing" + "secureCameraNotActiveOrAltBouncerIsShowing", + tableLogBuffer ), logAndObserve( biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture, - "isFaceAuthSupportedInCurrentPosture" + "isFaceAuthSupportedInCurrentPosture", + tableLogBuffer ), logAndObserve( biometricSettingsRepository.isCurrentUserInLockdown.isFalse(), - "userHasNotLockedDownDevice" + "userHasNotLockedDownDevice", + tableLogBuffer ) ) .reduce(::and) @@ -296,20 +317,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) @@ -323,6 +351,7 @@ constructor( cancel() } } + .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false) .launchIn(applicationScope) } @@ -337,7 +366,6 @@ constructor( override fun onAuthenticationAcquired(acquireInfo: Int) { _authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo) - faceAuthLogger.authenticationAcquired(acquireInfo) } override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) { @@ -398,7 +426,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 } @@ -435,7 +463,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" + ) } } @@ -464,7 +501,7 @@ constructor( private val currentUserId: Int get() = userRepository.getSelectedUserInfo().id - fun cancelDetection() { + private fun cancelDetection() { detectCancellationSignal?.cancel() detectCancellationSignal = null } @@ -488,10 +525,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/core/java/com/android/internal/expresslog/Utils.java b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt index d82192f51662..342064f02ef2 100644 --- a/core/java/com/android/internal/expresslog/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt @@ -14,8 +14,12 @@ * limitations under the License. */ -package com.android.internal.expresslog; +package com.android.systemui.keyguard.data.repository -final class Utils { - static native long hashString(String stringToHash); -} +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/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/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt index dbc2a5ec4e0a..b29b5887545a 100644 --- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt @@ -19,7 +19,7 @@ package com.android.systemui.media.taptotransfer.common import android.content.Context import android.content.pm.PackageManager import android.graphics.drawable.Drawable -import androidx.annotation.ColorRes +import androidx.annotation.AttrRes import androidx.annotation.DrawableRes import com.android.systemui.R import com.android.systemui.common.shared.model.ContentDescription @@ -108,7 +108,7 @@ class MediaTttUtils { data class IconInfo( val contentDescription: ContentDescription, val icon: MediaTttIcon, - @ColorRes val tint: Int?, + @AttrRes val tint: Int?, /** * True if [drawable] is the app's icon, and false if [drawable] is some generic default icon. */ diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index 26b0e8dd9895..b9ef916eebdf 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -55,6 +55,7 @@ import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; +import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.WindowInsets; import android.view.WindowManager; @@ -173,7 +174,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } }; - + private final VelocityTracker mVelocityTracker = VelocityTracker.obtain(); private final Context mContext; private final UserTracker mUserTracker; private final OverviewProxyService mOverviewProxyService; @@ -901,6 +902,10 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack Log.d(DEBUG_MISSING_GESTURE_TAG, "Start gesture: " + ev); } + // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new + // ACTION_DOWN, in that case we should just reuse the old instance. + mVelocityTracker.clear(); + // Verify if this is in within the touch region and we aren't in immersive mode, and // either the bouncer is showing or the notification panel is hidden mInputEventReceiver.setBatchingEnabled(false); @@ -1027,11 +1032,30 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack private void dispatchToBackAnimation(MotionEvent event) { if (mBackAnimation != null) { + mVelocityTracker.addMovement(event); + + final float velocityX; + final float velocityY; + if (event.getAction() == MotionEvent.ACTION_UP) { + // Compute the current velocity is expensive (see computeCurrentVelocity), so we + // are only doing it when the user completes the gesture. + int unitPixelPerSecond = 1000; + int maxVelocity = mViewConfiguration.getScaledMaximumFlingVelocity(); + mVelocityTracker.computeCurrentVelocity(unitPixelPerSecond, maxVelocity); + velocityX = mVelocityTracker.getXVelocity(); + velocityY = mVelocityTracker.getYVelocity(); + } else { + velocityX = Float.NaN; + velocityY = Float.NaN; + } + mBackAnimation.onBackMotion( - event.getX(), - event.getY(), - event.getActionMasked(), - mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT); + /* touchX = */ event.getX(), + /* touchY = */ event.getY(), + /* velocityX = */ velocityX, + /* velocityY = */ velocityY, + /* keyAction = */ event.getActionMasked(), + /* swipeEdge = */ mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT); } } diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index c2c1306d2a32..a765702a95b2 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -18,6 +18,10 @@ package com.android.systemui.power; import static android.app.PendingIntent.FLAG_IMMUTABLE; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_CONFIRMATION; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_LOW_WARNING; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SaverManualEnabledReason; + import android.app.Dialog; import android.app.KeyguardManager; import android.app.Notification; @@ -691,7 +695,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { d.setTitle(R.string.battery_saver_confirmation_title); d.setPositiveButton(R.string.battery_saver_confirmation_ok, (dialog, which) -> { - setSaverMode(true, false); + setSaverMode(true, false, SAVER_ENABLED_CONFIRMATION); logEvent(BatteryWarningEvents.LowBatteryWarningEvent.SAVER_CONFIRM_OK); }); d.setNegativeButton(android.R.string.cancel, (dialog, which) -> @@ -790,8 +794,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { return builder; } - private void setSaverMode(boolean mode, boolean needFirstTimeWarning) { - BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning); + private void setSaverMode(boolean mode, boolean needFirstTimeWarning, + @SaverManualEnabledReason int reason) { + BatterySaverUtils.setPowerSaveMode(mContext, mode, needFirstTimeWarning, reason); } private void startBatterySaverSchedulePage() { @@ -839,7 +844,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { } else if (action.equals(ACTION_START_SAVER)) { logEvent(BatteryWarningEvents .LowBatteryWarningEvent.LOW_BATTERY_NOTIFICATION_TURN_ON); - setSaverMode(true, true); + setSaverMode(true, true, SAVER_ENABLED_LOW_WARNING); dismissLowBatteryNotification(); } else if (action.equals(ACTION_SHOW_START_SAVER_CONFIRMATION)) { dismissLowBatteryNotification(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java index 57b479e6899f..856c64a52290 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java @@ -20,8 +20,6 @@ import android.content.res.Resources; import android.os.Build; import android.provider.Settings; -import com.android.internal.logging.InstanceId; -import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; @@ -55,11 +53,9 @@ public interface QSHost { return tiles; } - void warn(String message, Throwable t); Context getContext(); Context getUserContext(); int getUserId(); - UiEventLogger getUiEventLogger(); Collection<QSTile> getTiles(); void addCallback(Callback callback); void removeCallback(Callback callback); @@ -107,8 +103,6 @@ public interface QSHost { int indexOf(String tileSpec); - InstanceId getNewInstanceId(); - interface Callback { void onTilesChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt index 14acb4b3ccf4..67927a4153b3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt @@ -19,8 +19,6 @@ package com.android.systemui.qs import android.content.ComponentName import android.content.Context import androidx.annotation.GuardedBy -import com.android.internal.logging.InstanceId -import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dump.DumpManager @@ -39,10 +37,9 @@ import kotlinx.coroutines.launch /** * Adapter to determine what real class to use for classes that depend on [QSHost]. - * * * When [Flags.QS_PIPELINE_NEW_HOST] is off, all calls will be routed to [QSTileHost]. * * When [Flags.QS_PIPELINE_NEW_HOST] is on, calls regarding the current set of tiles will be - * routed to [CurrentTilesInteractor]. Other calls (like [warn]) will still be routed to + * routed to [CurrentTilesInteractor]. Other calls (like [createTileView]) will still be routed to * [QSTileHost]. * * This routing also includes dumps. @@ -71,10 +68,7 @@ constructor( init { scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() } // Redirect dump to the correct host (needed for CTS tests) - dumpManager.registerCriticalDumpable( - TAG, - if (useNewHost) interactor else qsTileHost - ) + dumpManager.registerCriticalDumpable(TAG, if (useNewHost) interactor else qsTileHost) } override fun getTiles(): Collection<QSTile> { @@ -103,10 +97,7 @@ constructor( override fun addCallback(callback: QSHost.Callback) { if (useNewHost) { - val job = - scope.launch { - interactor.currentTiles.collect { callback.onTilesChanged() } - } + val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } } synchronized(callbacksMap) { callbacksMap.put(callback, job) } } else { qsTileHost.addCallback(callback) @@ -147,10 +138,7 @@ constructor( override fun addTile(component: ComponentName, end: Boolean) { if (useNewHost) { - interactor.addTile( - TileSpec.create(component), - if (end) POSITION_AT_END else 0 - ) + interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0) } else { qsTileHost.addTile(component, end) } @@ -164,10 +152,6 @@ constructor( } } - override fun warn(message: String?, t: Throwable?) { - qsTileHost.warn(message, t) - } - override fun getContext(): Context { return if (useNewHost) { context @@ -192,10 +176,6 @@ constructor( } } - override fun getUiEventLogger(): UiEventLogger { - return qsTileHost.uiEventLogger - } - override fun createTileView( themedContext: Context?, tile: QSTile?, @@ -219,8 +199,4 @@ constructor( override fun indexOf(tileSpec: String): Int { return specs.indexOf(tileSpec) } - - override fun getNewInstanceId(): InstanceId { - return qsTileHost.newInstanceId - } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 0ca897373d13..59b94b7c4bd4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -29,9 +29,6 @@ import androidx.annotation.MainThread; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.logging.InstanceId; -import com.android.internal.logging.InstanceIdSequence; -import com.android.internal.logging.UiEventLogger; import com.android.systemui.Dumpable; import com.android.systemui.ProtoDumpable; import com.android.systemui.R; @@ -91,7 +88,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P PanelInteractor, CustomTileAddedRepository { private static final String TAG = "QSTileHost"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - private static final int MAX_QS_INSTANCE_ID = 1 << 20; // Shared prefs that hold tile lifecycle info. @VisibleForTesting @@ -103,8 +99,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P private final TunerService mTunerService; private final PluginManager mPluginManager; private final QSLogger mQSLogger; - private final UiEventLogger mUiEventLogger; - private final InstanceIdSequence mInstanceIdSequence; private final CustomTileStatePersister mCustomTileStatePersister; private final Executor mMainExecutor; private final UserFileManager mUserFileManager; @@ -137,7 +131,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P Provider<AutoTileManager> autoTiles, Optional<CentralSurfaces> centralSurfacesOptional, QSLogger qsLogger, - UiEventLogger uiEventLogger, UserTracker userTracker, SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister, @@ -150,13 +143,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P mTunerService = tunerService; mPluginManager = pluginManager; mQSLogger = qsLogger; - mUiEventLogger = uiEventLogger; mMainExecutor = mainExecutor; mTileLifeCycleManagerFactory = tileLifecycleManagerFactory; mUserFileManager = userFileManager; mFeatureFlags = featureFlags; - mInstanceIdSequence = new InstanceIdSequence(MAX_QS_INSTANCE_ID); mCentralSurfacesOptional = centralSurfacesOptional; mQsFactories.add(defaultFactory); @@ -175,11 +166,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P }); } - @Override - public InstanceId getNewInstanceId() { - return mInstanceIdSequence.newInstanceId(); - } - public void destroy() { mTiles.values().forEach(tile -> tile.destroy()); mAutoTiles.destroy(); @@ -207,11 +193,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P } @Override - public UiEventLogger getUiEventLogger() { - return mUiEventLogger; - } - - @Override public void addCallback(Callback callback) { mCallbacks.add(callback); } @@ -227,11 +208,6 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P } @Override - public void warn(String message, Throwable t) { - // already logged - } - - @Override public void collapsePanels() { mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateCollapsePanels); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt new file mode 100644 index 000000000000..fc739edc69d2 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/QsEventLogger.kt @@ -0,0 +1,44 @@ +/* + * 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 + +import com.android.internal.logging.InstanceId +import com.android.internal.logging.InstanceIdSequence +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +interface QsEventLogger : UiEventLogger { + fun getNewInstanceId(): InstanceId +} + +@SysUISingleton +class QsEventLoggerImpl +@Inject +constructor( + uiEventLogger: UiEventLogger, +) : QsEventLogger, UiEventLogger by uiEventLogger { + + companion object { + private const val MAX_QS_INSTANCE_ID = 1 shl 20 + } + + val sequence = InstanceIdSequence(MAX_QS_INSTANCE_ID) + override fun getNewInstanceId(): InstanceId { + return sequence.newInstanceId() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt index 3ddd9f1cfe5f..1f63f5da2f2b 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt @@ -21,6 +21,8 @@ import com.android.systemui.flags.Flags import com.android.systemui.qs.QSHost import com.android.systemui.qs.QSHostAdapter import com.android.systemui.qs.QSTileHost +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.QsEventLoggerImpl import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor @@ -34,8 +36,12 @@ interface QSHostModule { @Binds fun provideQsHost(controllerImpl: QSHostAdapter): QSHost + @Binds fun provideEventLogger(impl: QsEventLoggerImpl): QsEventLogger + @Module companion object { + private const val MAX_QS_INSTANCE_ID = 1 shl 20 + @Provides @JvmStatic fun providePanelInteractor( diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java index d4854e1a7daf..897b0e73dca0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/CustomTile.java @@ -59,17 +59,20 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.external.TileLifecycleManager.TileChangeListener; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.DisplayTracker; +import dagger.Lazy; + import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import javax.inject.Inject; -import dagger.Lazy; + public class CustomTile extends QSTileImpl<State> implements TileChangeListener { public static final String PREFIX = "custom("; @@ -111,6 +114,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener private CustomTile( QSHost host, + QsEventLogger uiEventLogger, Looper backgroundLooper, Handler mainHandler, FalsingManager falsingManager, @@ -124,7 +128,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener TileServices tileServices, DisplayTracker displayTracker ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mTileServices = tileServices; mWindowManager = WindowManagerGlobal.getWindowManagerService(); @@ -561,6 +565,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener public static class Builder { final Lazy<QSHost> mQSHostLazy; + final QsEventLogger mUiEventLogger; final Looper mBackgroundLooper; final Handler mMainHandler; private final FalsingManager mFalsingManager; @@ -578,6 +583,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener @Inject public Builder( Lazy<QSHost> hostLazy, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -590,6 +596,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener DisplayTracker displayTracker ) { mQSHostLazy = hostLazy; + mUiEventLogger = uiEventLogger; mBackgroundLooper = backgroundLooper; mMainHandler = mainHandler; mFalsingManager = falsingManager; @@ -620,6 +627,7 @@ public class CustomTile extends QSTileImpl<State> implements TileChangeListener String action = getAction(mSpec); return new CustomTile( mQSHostLazy.get(), + mUiEventLogger, mBackgroundLooper, mMainHandler, mFalsingManager, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java index 49ba5086f64d..2a9e7d05c187 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileImpl.java @@ -66,6 +66,7 @@ import com.android.systemui.plugins.qs.QSTile.State; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSEvent; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SideLabelTileLayout; import com.android.systemui.qs.logging.QSLogger; @@ -179,6 +180,7 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy protected QSTileImpl( QSHost host, + QsEventLogger uiEventLogger, Looper backgroundLooper, Handler mainHandler, FalsingManager falsingManager, @@ -189,8 +191,8 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy ) { mHost = host; mContext = host.getContext(); - mInstanceId = host.getNewInstanceId(); - mUiEventLogger = host.getUiEventLogger(); + mInstanceId = uiEventLogger.getNewInstanceId(); + mUiEventLogger = uiEventLogger; mUiHandler = mainHandler; mHandler = new H(backgroundLooper); @@ -633,7 +635,6 @@ public abstract class QSTileImpl<TState extends State> implements QSTile, Lifecy } catch (Throwable t) { final String error = "Error in " + name; Log.w(TAG, error, t); - mHost.warn(error, t); } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java index 92a83bba8a68..30765f7f974d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java @@ -45,15 +45,18 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.GlobalSettings; +import dagger.Lazy; + import javax.inject.Inject; -import dagger.Lazy; + /** Quick settings tile: Airplane mode **/ public class AirplaneModeTile extends QSTileImpl<BooleanState> { @@ -69,6 +72,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { @Inject public AirplaneModeTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -81,7 +85,7 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> { GlobalSettings globalSettings, UserTracker userTracker ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mBroadcastDispatcher = broadcastDispatcher; mLazyConnectivityManager = lazyConnectivityManager; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt index 2ca452e45ecf..c709969ec6e2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt @@ -22,6 +22,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.settings.UserTracker @@ -31,6 +32,7 @@ import javax.inject.Inject class AlarmTile @Inject constructor( host: QSHost, + uiEventLogger: QsEventLogger, @Background backgroundLooper: Looper, @Main mainHandler: Handler, falsingManager: FalsingManager, @@ -42,6 +44,7 @@ class AlarmTile @Inject constructor( nextAlarmController: NextAlarmController ) : QSTileImpl<QSTile.State>( host, + uiEventLogger, backgroundLooper, mainHandler, falsingManager, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java index 027a464251c9..a444e7631527 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -62,6 +63,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements @Inject public BatterySaverTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -72,7 +74,7 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements BatteryController batteryController, SecureSettings secureSettings ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mBatteryController = batteryController; mBatteryController.observe(getLifecycle(), this); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java index 08fe2709b810..30218a6a3180 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java @@ -47,6 +47,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.BluetoothController; @@ -74,6 +75,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { @Inject public BluetoothTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -83,7 +85,7 @@ public class BluetoothTile extends QSTileImpl<BooleanState> { QSLogger qsLogger, BluetoothController bluetoothController ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = bluetoothController; mController.observe(getLifecycle(), mCallback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java index 93e5f1efbdc8..65ef6b9c31a5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -48,7 +49,9 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { public static final String TILE_SPEC = "cameratoggle"; @Inject - protected CameraToggleTile(QSHost host, + protected CameraToggleTile( + QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, MetricsLogger metricsLogger, @@ -58,7 +61,7 @@ public class CameraToggleTile extends SensorPrivacyToggleTile { QSLogger qsLogger, IndividualSensorPrivacyController sensorPrivacyController, KeyguardStateController keyguardStateController) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger, sensorPrivacyController, keyguardStateController); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index 8d984817ba77..54376fe604df 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -47,6 +47,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.connectivity.NetworkController; @@ -84,6 +85,7 @@ public class CastTile extends QSTileImpl<BooleanState> { @Inject public CastTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -97,7 +99,7 @@ public class CastTile extends QSTileImpl<BooleanState> { HotspotController hotspotController, DialogLaunchAnimator dialogLaunchAnimator ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = castController; mKeyguard = keyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java index b6205d5df63d..cf9e3468c8f5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorCorrectionTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -56,6 +57,7 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> { @Inject public ColorCorrectionTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -66,7 +68,7 @@ public class ColorCorrectionTile extends QSTileImpl<BooleanState> { UserTracker userTracker, SecureSettings secureSettings ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mSetting = new SettingObserver(secureSettings, mHandler, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java index 9a44e83ad9a1..4ecde6123dd5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ColorInversionTile.java @@ -38,6 +38,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -55,6 +56,7 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { @Inject public ColorInversionTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -65,7 +67,7 @@ public class ColorInversionTile extends QSTileImpl<BooleanState> { UserTracker userTracker, SecureSettings secureSettings ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mSetting = new SettingObserver(secureSettings, mHandler, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java index add517e18516..e769b5ef9989 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java @@ -38,6 +38,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.phone.SystemUIDialog; @@ -58,6 +59,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements @Inject public DataSaverTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -68,7 +70,7 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements DataSaverController dataSaverController, DialogLaunchAnimator dialogLaunchAnimator ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mDataSaverController = dataSaverController; mDialogLaunchAnimator = dialogLaunchAnimator; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt index 01164fb0a009..ddaff3bb9a64 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt @@ -40,6 +40,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import java.util.concurrent.atomic.AtomicBoolean @@ -47,6 +48,7 @@ import javax.inject.Inject class DeviceControlsTile @Inject constructor( host: QSHost, + uiEventLogger: QsEventLogger, @Background backgroundLooper: Looper, @Main mainHandler: Handler, falsingManager: FalsingManager, @@ -56,14 +58,15 @@ class DeviceControlsTile @Inject constructor( qsLogger: QSLogger, private val controlsComponent: ControlsComponent ) : QSTileImpl<QSTile.State>( - host, - backgroundLooper, - mainHandler, - falsingManager, - metricsLogger, - statusBarStateController, - activityStarter, - qsLogger + host, + uiEventLogger, + backgroundLooper, + mainHandler, + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger ) { private var hasControlsApps = AtomicBoolean(false) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java index 434fe45f47c0..3e7bdd1a1444 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java @@ -54,6 +54,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -88,6 +89,7 @@ public class DndTile extends QSTileImpl<BooleanState> { @Inject public DndTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -100,7 +102,7 @@ public class DndTile extends QSTileImpl<BooleanState> { SecureSettings secureSettings, DialogLaunchAnimator dialogLaunchAnimator ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = zenModeController; mSharedPreferences = sharedPreferences; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java index f913326a6a67..eef4c1dd4436 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java @@ -48,6 +48,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -90,6 +91,7 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> { @Inject public DreamTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -105,7 +107,7 @@ public class DreamTile extends QSTileImpl<QSTile.BooleanState> { @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_DOCK_USER) boolean dreamOnlyEnabledForDockUser ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mDreamManager = dreamManager; mBroadcastDispatcher = broadcastDispatcher; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java index e091a750fbec..2c986da8370a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FlashlightTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.FlashlightController; @@ -55,6 +56,7 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements @Inject public FlashlightTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -64,7 +66,7 @@ public class FlashlightTile extends QSTileImpl<BooleanState> implements QSLogger qsLogger, FlashlightController flashlightController ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mFlashlightController = flashlightController; mFlashlightController.observe(getLifecycle(), this); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt index 3f514344cee1..12d98473ff07 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt @@ -36,6 +36,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.phone.SystemUIDialog @@ -47,6 +48,7 @@ class FontScalingTile @Inject constructor( host: QSHost, + uiEventLogger: QsEventLogger, @Background backgroundLooper: Looper, @Main mainHandler: Handler, falsingManager: FalsingManager, @@ -61,6 +63,7 @@ constructor( ) : QSTileImpl<QSTile.State?>( host, + uiEventLogger, backgroundLooper, mainHandler, falsingManager, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java index 6bf8b7666054..4c3699ced6e5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java @@ -41,6 +41,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.DataSaverController; @@ -61,6 +62,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> { @Inject public HotspotTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -71,7 +73,7 @@ public class HotspotTile extends QSTileImpl<BooleanState> { HotspotController hotspotController, DataSaverController dataSaverController ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mHotspotController = hotspotController; mDataSaverController = dataSaverController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java index 75d01723667d..f16f0dcc5dba 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/InternetTile.java @@ -51,6 +51,7 @@ import com.android.systemui.plugins.qs.QSTile.SignalState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.AlphaControlledSignalTileView; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; @@ -90,6 +91,7 @@ public class InternetTile extends QSTileImpl<SignalState> { @Inject public InternetTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -101,7 +103,7 @@ public class InternetTile extends QSTileImpl<SignalState> { AccessPointController accessPointController, InternetDialogFactory internetDialogFactory ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mInternetDialogFactory = internetDialogFactory; mHandler = mainHandler; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index 27f58269722a..83c568878c94 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -59,6 +60,7 @@ public class LocationTile extends QSTileImpl<BooleanState> { @Inject public LocationTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -70,7 +72,7 @@ public class LocationTile extends QSTileImpl<BooleanState> { KeyguardStateController keyguardStateController, PanelInteractor panelInteractor ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = locationController; mKeyguard = keyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java index 2e475d40d55f..86a6a8c6a2b6 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -48,7 +49,9 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { public static final String TILE_SPEC = "mictoggle"; @Inject - protected MicrophoneToggleTile(QSHost host, + protected MicrophoneToggleTile( + QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, MetricsLogger metricsLogger, @@ -58,7 +61,7 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile { QSLogger qsLogger, IndividualSensorPrivacyController sensorPrivacyController, KeyguardStateController keyguardStateController) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger, sensorPrivacyController, keyguardStateController); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java index e189f80a7c23..29ccb76de820 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NfcTile.java @@ -43,6 +43,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -65,6 +66,7 @@ public class NfcTile extends QSTileImpl<BooleanState> { @Inject public NfcTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -74,7 +76,7 @@ public class NfcTile extends QSTileImpl<BooleanState> { QSLogger qsLogger, BroadcastDispatcher broadcastDispatcher ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mBroadcastDispatcher = broadcastDispatcher; } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java index aacd53bde51c..405e1394871c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/NightDisplayTile.java @@ -45,6 +45,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.LocationController; @@ -80,6 +81,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements @Inject public NightDisplayTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -91,7 +93,7 @@ public class NightDisplayTile extends QSTileImpl<BooleanState> implements ColorDisplayManager colorDisplayManager, NightDisplayListenerModule.Builder nightDisplayListenerBuilder ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mLocationController = locationController; mManager = colorDisplayManager; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java index ae67d99ea2fb..1eb317aaeae8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/OneHandedModeTile.java @@ -36,6 +36,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -57,6 +58,7 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> { @Inject public OneHandedModeTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -66,7 +68,7 @@ public class OneHandedModeTile extends QSTileImpl<BooleanState> { QSLogger qsLogger, UserTracker userTracker, SecureSettings secureSettings) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mSetting = new SettingObserver(secureSettings, mHandler, Settings.Secure.ONE_HANDED_MODE_ENABLED, userTracker.getUserId()) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java index 92f52724ca0c..9e365d34bed9 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QRCodeScannerTile.java @@ -37,6 +37,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qrcodescanner.controller.QRCodeScannerController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -62,6 +63,7 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> { @Inject public QRCodeScannerTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -70,7 +72,7 @@ public class QRCodeScannerTile extends QSTileImpl<QSTile.State> { ActivityStarter activityStarter, QSLogger qsLogger, QRCodeScannerController qrCodeScannerController) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mQRCodeScannerController = qrCodeScannerController; mQRCodeScannerController.observe(getLifecycle(), mCallback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index 4a3c56328006..e026bdbc2e08 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -49,6 +49,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -83,6 +84,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { @Inject public QuickAccessWalletTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -94,7 +96,7 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { PackageManager packageManager, SecureSettings secureSettings, QuickAccessWalletController quickAccessWalletController) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = quickAccessWalletController; mKeyguardStateController = keyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java index 10f1ce4946c8..2e04afb0048a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ReduceBrightColorsTile.java @@ -38,6 +38,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -59,6 +60,7 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> @Named(RBC_AVAILABLE) boolean isAvailable, ReduceBrightColorsController reduceBrightColorsController, QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -67,7 +69,7 @@ public class ReduceBrightColorsTile extends QSTileImpl<QSTile.BooleanState> ActivityStarter activityStarter, QSLogger qsLogger ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mReduceBrightColorsController = reduceBrightColorsController; mReduceBrightColorsController.observe(getLifecycle(), this); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java index 8888c733c3c1..7f7f8ad6a4c1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java @@ -44,6 +44,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -71,6 +72,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements @Inject public RotationLockTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -83,7 +85,7 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements BatteryController batteryController, SecureSettings secureSettings ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = rotationLockController; mController.observe(this, mCallback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index 65592a717565..2d4652db6b80 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -41,6 +41,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -74,6 +75,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> @Inject public ScreenRecordTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -88,7 +90,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> DialogLaunchAnimator dialogLaunchAnimator, PanelInteractor panelInteractor ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = controller; mController.observe(this, mCallback); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index d99c1d1daf7e..7c4f097a1724 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -39,6 +39,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController; @@ -68,7 +69,9 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS */ public abstract String getRestriction(); - protected SensorPrivacyToggleTile(QSHost host, + protected SensorPrivacyToggleTile( + QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -78,7 +81,7 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS QSLogger qsLogger, IndividualSensorPrivacyController sensorPrivacyController, KeyguardStateController keyguardStateController) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mSensorPrivacyController = sensorPrivacyController; mKeyguard = keyguardStateController; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java index 809689cb806d..a60d1ad448b4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/UiModeNightTile.java @@ -39,6 +39,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.BatteryController; @@ -69,6 +70,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements @Inject public UiModeNightTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -80,7 +82,7 @@ public class UiModeNightTile extends QSTileImpl<QSTile.BooleanState> implements BatteryController batteryController, LocationController locationController ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mBatteryController = batteryController; mUiModeManager = host.getUserContext().getSystemService(UiModeManager.class); diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java index 6a5c99032457..17e72e597e58 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/WorkModeTile.java @@ -40,6 +40,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.phone.ManagedProfileController; @@ -59,6 +60,7 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements @Inject public WorkModeTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -68,7 +70,7 @@ public class WorkModeTile extends QSTileImpl<BooleanState> implements QSLogger qsLogger, ManagedProfileController managedProfileController ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mProfileController = managedProfileController; mProfileController.observe(getLifecycle(), this); diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index aedd9762a601..222a0f4fa248 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -3333,7 +3333,10 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump mGestureRecorder = recorder; mHideExpandedRunnable = hideExpandedRunnable; - mNotificationStackScrollLayoutController.setShelfController(notificationShelfController); + if (!mFeatureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + mNotificationStackScrollLayoutController.setShelfController( + notificationShelfController); + } mNotificationShelfController = notificationShelfController; mLockscreenShadeTransitionController.bindController(notificationShelfController); updateMaxDisplayedNotifications(true); 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/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java index 9b1e2faf3b69..142689e88b51 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java @@ -1423,7 +1423,7 @@ public class KeyguardIndicationController { private boolean canUnlockWithFingerprint() { return mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser()); + getCurrentUser()) && mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed(); } private void showErrorMessageNowOrLater(String errString, @Nullable String followUpMsg) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java index e6715a133838..7eb63da38028 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java @@ -24,6 +24,7 @@ import android.content.res.Resources; import android.graphics.Rect; import android.util.AttributeSet; import android.util.IndentingPrintWriter; +import android.util.Log; import android.util.MathUtils; import android.view.View; import android.view.ViewGroup; @@ -52,6 +53,8 @@ import com.android.systemui.statusbar.notification.row.ExpandableView; import com.android.systemui.statusbar.notification.stack.AmbientState; import com.android.systemui.statusbar.notification.stack.AnimationProperties; import com.android.systemui.statusbar.notification.stack.ExpandableViewState; +import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager; +import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController; import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm; import com.android.systemui.statusbar.notification.stack.ViewState; @@ -96,6 +99,11 @@ public class NotificationShelf extends ActivatableNotificationView implements St private NotificationShelfController mController; private float mActualWidth = -1; private boolean mSensitiveRevealAnimEndabled; + private boolean mShelfRefactorFlagEnabled; + private boolean mCanModifyColorOfNotifications; + private boolean mCanInteract; + private NotificationStackScrollLayout mHostLayout; + private NotificationRoundnessManager mRoundnessManager; public NotificationShelf(Context context, AttributeSet attrs) { super(context, attrs); @@ -132,6 +140,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St public void bind(AmbientState ambientState, NotificationStackScrollLayoutController hostLayoutController) { + assertRefactorFlagDisabled(); mAmbientState = ambientState; mHostLayoutController = hostLayoutController; hostLayoutController.setOnNotificationRemovedListener((child, isTransferInProgress) -> { @@ -139,6 +148,14 @@ public class NotificationShelf extends ActivatableNotificationView implements St }); } + public void bind(AmbientState ambientState, NotificationStackScrollLayout hostLayout, + NotificationRoundnessManager roundnessManager) { + if (!checkRefactorFlagEnabled()) return; + mAmbientState = ambientState; + mHostLayout = hostLayout; + mRoundnessManager = roundnessManager; + } + private void updateResources() { Resources res = getResources(); mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mContext); @@ -230,7 +247,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St } else { viewState.setAlpha(1f - ambientState.getHideAmount()); } - viewState.belowSpeedBump = mHostLayoutController.getSpeedBumpIndex() == 0; + viewState.belowSpeedBump = getSpeedBumpIndex() == 0; viewState.hideSensitive = false; viewState.setXTranslation(getTranslationX()); viewState.hasItemsInStableShelf = lastViewState.inShelf; @@ -273,6 +290,14 @@ public class NotificationShelf extends ActivatableNotificationView implements St } } + private int getSpeedBumpIndex() { + if (mShelfRefactorFlagEnabled) { + return mHostLayout.getSpeedBumpIndex(); + } else { + return mHostLayoutController.getSpeedBumpIndex(); + } + } + /** * @param fractionToShade Fraction of lockscreen to shade transition * @param shortestWidth Shortest width to use for lockscreen shelf @@ -385,8 +410,8 @@ public class NotificationShelf extends ActivatableNotificationView implements St int baseZHeight = mAmbientState.getBaseZHeight(); int clipTopAmount = 0; - for (int i = 0; i < mHostLayoutController.getChildCount(); i++) { - ExpandableView child = mHostLayoutController.getChildAt(i); + for (int i = 0; i < getHostLayoutChildCount(); i++) { + ExpandableView child = getHostLayoutChildAt(i); if (!child.needsClippingToShelf() || child.getVisibility() == GONE) { continue; } @@ -425,7 +450,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St transitionAmount = inShelfAmount; } // We don't want to modify the color if the notification is hun'd - if (isLastChild && mController.canModifyColorOfNotifications()) { + if (isLastChild && canModifyColorOfNotifications()) { if (colorOfViewBeforeLast == NO_COLOR) { colorOfViewBeforeLast = ownColorUntinted; } @@ -471,11 +496,11 @@ public class NotificationShelf extends ActivatableNotificationView implements St // TODO(b/172289889) transition last icon in shelf to notification icon and vice versa. setVisibility(isHidden ? View.INVISIBLE : View.VISIBLE); - mShelfIcons.setSpeedBumpIndex(mHostLayoutController.getSpeedBumpIndex()); + mShelfIcons.setSpeedBumpIndex(getSpeedBumpIndex()); mShelfIcons.calculateIconXTranslations(); mShelfIcons.applyIconStates(); - for (int i = 0; i < mHostLayoutController.getChildCount(); i++) { - View child = mHostLayoutController.getChildAt(i); + for (int i = 0; i < getHostLayoutChildCount(); i++) { + View child = getHostLayoutChildAt(i); if (!(child instanceof ExpandableNotificationRow) || child.getVisibility() == GONE) { continue; @@ -490,6 +515,30 @@ public class NotificationShelf extends ActivatableNotificationView implements St } } + private ExpandableView getHostLayoutChildAt(int index) { + if (mShelfRefactorFlagEnabled) { + return (ExpandableView) mHostLayout.getChildAt(index); + } else { + return mHostLayoutController.getChildAt(index); + } + } + + private int getHostLayoutChildCount() { + if (mShelfRefactorFlagEnabled) { + return mHostLayout.getChildCount(); + } else { + return mHostLayoutController.getChildCount(); + } + } + + private boolean canModifyColorOfNotifications() { + if (mShelfRefactorFlagEnabled) { + return mCanModifyColorOfNotifications && mAmbientState.isShadeExpanded(); + } else { + return mController.canModifyColorOfNotifications(); + } + } + private void updateCornerRoundnessOnScroll( ActivatableNotificationView anv, float viewStart, @@ -504,7 +553,7 @@ public class NotificationShelf extends ActivatableNotificationView implements St && anv == mAmbientState.getTrackedHeadsUpRow(); final boolean shouldUpdateCornerRoundness = viewStart < shelfStart - && !mHostLayoutController.isViewAffectedBySwipe(anv) + && !isViewAffectedBySwipe(anv) && !isUnlockedHeadsUp && !isHunGoingToShade && !anv.isAboveShelf() @@ -556,6 +605,14 @@ public class NotificationShelf extends ActivatableNotificationView implements St anv.requestBottomRoundness(bottomValue, sourceType, /* animate = */ false); } + private boolean isViewAffectedBySwipe(ExpandableView expandableView) { + if (!mShelfRefactorFlagEnabled) { + return mHostLayoutController.isViewAffectedBySwipe(expandableView); + } else { + return mRoundnessManager.isViewAffectedBySwipe(expandableView); + } + } + /** * Clips transient views to the top of the shelf - Transient views are only used for * disappearing views/animations and need to be clipped correctly by the shelf to ensure they @@ -563,8 +620,8 @@ public class NotificationShelf extends ActivatableNotificationView implements St * swipes quickly. */ private void clipTransientViews() { - for (int i = 0; i < mHostLayoutController.getTransientViewCount(); i++) { - View transientView = mHostLayoutController.getTransientView(i); + for (int i = 0; i < getHostLayoutTransientViewCount(); i++) { + View transientView = getHostLayoutTransientView(i); if (transientView instanceof ExpandableView) { ExpandableView transientExpandableView = (ExpandableView) transientView; updateNotificationClipHeight(transientExpandableView, getTranslationY(), -1); @@ -572,6 +629,22 @@ public class NotificationShelf extends ActivatableNotificationView implements St } } + private View getHostLayoutTransientView(int index) { + if (mShelfRefactorFlagEnabled) { + return mHostLayout.getTransientView(index); + } else { + return mHostLayoutController.getTransientView(index); + } + } + + private int getHostLayoutTransientViewCount() { + if (mShelfRefactorFlagEnabled) { + return mHostLayout.getTransientViewCount(); + } else { + return mHostLayoutController.getTransientViewCount(); + } + } + private void updateIconClipAmount(ExpandableNotificationRow row) { float maxTop = row.getTranslationY(); if (getClipTopAmount() != 0) { @@ -911,18 +984,27 @@ public class NotificationShelf extends ActivatableNotificationView implements St @Override public void onStateChanged(int newState) { + assertRefactorFlagDisabled(); mStatusBarState = newState; updateInteractiveness(); } private void updateInteractiveness() { - mInteractive = mStatusBarState == StatusBarState.KEYGUARD && mHasItemsInStableShelf; + mInteractive = canInteract() && mHasItemsInStableShelf; setClickable(mInteractive); setFocusable(mInteractive); setImportantForAccessibility(mInteractive ? View.IMPORTANT_FOR_ACCESSIBILITY_YES : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); } + private boolean canInteract() { + if (mShelfRefactorFlagEnabled) { + return mCanInteract; + } else { + return mStatusBarState == StatusBarState.KEYGUARD; + } + } + @Override protected boolean isInteractive() { return mInteractive; @@ -959,12 +1041,46 @@ public class NotificationShelf extends ActivatableNotificationView implements St return false; } + private void assertRefactorFlagDisabled() { + if (mShelfRefactorFlagEnabled) { + NotificationShelfController.throwIllegalFlagStateError(false); + } + } + + private boolean checkRefactorFlagEnabled() { + if (!mShelfRefactorFlagEnabled) { + Log.wtf(TAG, + "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is disabled."); + } + return mShelfRefactorFlagEnabled; + } + public void setController(NotificationShelfController notificationShelfController) { + assertRefactorFlagDisabled(); mController = notificationShelfController; } + public void setCanModifyColorOfNotifications(boolean canModifyColorOfNotifications) { + if (!checkRefactorFlagEnabled()) return; + mCanModifyColorOfNotifications = canModifyColorOfNotifications; + } + + public void setCanInteract(boolean canInteract) { + if (!checkRefactorFlagEnabled()) return; + mCanInteract = canInteract; + updateInteractiveness(); + } + public void setIndexOfFirstViewInShelf(ExpandableView firstViewInShelf) { - mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf); + mIndexOfFirstViewInShelf = getIndexOfViewInHostLayout(firstViewInShelf); + } + + private int getIndexOfViewInHostLayout(ExpandableView child) { + if (mShelfRefactorFlagEnabled) { + return mHostLayout.indexOfChild(child); + } else { + return mHostLayoutController.indexOfChild(child); + } } /** @@ -975,6 +1091,15 @@ public class NotificationShelf extends ActivatableNotificationView implements St mSensitiveRevealAnimEndabled = enabled; } + public void setRefactorFlagEnabled(boolean enabled) { + mShelfRefactorFlagEnabled = enabled; + } + + public void requestRoundnessResetFor(ExpandableView child) { + if (!checkRefactorFlagEnabled()) return; + child.requestRoundnessReset(SHELF_SCROLL); + } + /** * This method resets the OnScroll roundness of a view to 0f * <p> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt index bf3d47c4a9ca..07cfd0d8019c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelfController.kt @@ -16,8 +16,11 @@ package com.android.systemui.statusbar +import android.util.Log import android.view.View import android.view.View.OnClickListener +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags import com.android.systemui.statusbar.notification.row.ActivatableNotificationView import com.android.systemui.statusbar.notification.row.ActivatableNotificationView.OnActivatedListener import com.android.systemui.statusbar.notification.row.ExpandableView @@ -51,4 +54,29 @@ interface NotificationShelfController { /** @see View.setOnClickListener */ fun setOnClickListener(listener: OnClickListener) + + companion object { + @JvmStatic + fun assertRefactorFlagDisabled(featureFlags: FeatureFlags) { + if (featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) { + throwIllegalFlagStateError(expected = false) + } + } + + @JvmStatic + fun checkRefactorFlagEnabled(featureFlags: FeatureFlags): Boolean = + featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR).also { enabled -> + if (!enabled) { + Log.wtf("NotifShelf", getErrorMessage(expected = true)) + } + } + + @JvmStatic + fun throwIllegalFlagStateError(expected: Boolean): Nothing = + error(getErrorMessage(expected)) + + private fun getErrorMessage(expected: Boolean): String = + "Code path not supported when Flags.NOTIFICATION_SHELF_REFACTOR is " + + if (expected) "disabled" else "enabled" + } } 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/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index a9d125508397..950ab5d2f5ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -299,6 +299,16 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ private boolean mShowGroupBackgroundWhenExpanded; + /** + * True if we always show the collapsed layout on lockscreen because vertical space is low. + */ + private boolean mSaveSpaceOnLockscreen; + + /** + * True if we use intrinsic height regardless of vertical space available on lockscreen. + */ + private boolean mIgnoreLockscreenConstraints; + private OnClickListener mExpandClickListener = new OnClickListener() { @Override public void onClick(View v) { @@ -394,6 +404,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return mGroupExpansionChanging; } + public void setSaveSpaceOnLockscreen(boolean saveSpace) { + mSaveSpaceOnLockscreen = saveSpace; + } + + public boolean getSaveSpaceOnLockscreen() { + return mSaveSpaceOnLockscreen; + } + public void setGroupExpansionChanging(boolean changing) { mGroupExpansionChanging = changing; } @@ -419,15 +437,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView */ public void setAnimationRunning(boolean running) { // Sets animations running in the private/public layouts. - if (mFeatureFlags.isEnabled(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE)) { - for (NotificationContentView l : mLayouts) { - if (l != null) { - l.setContentAnimationRunning(running); - setIconAnimationRunning(running, l); - } - } - } else { - for (NotificationContentView l : mLayouts) { + for (NotificationContentView l : mLayouts) { + if (l != null) { + l.setContentAnimationRunning(running); setIconAnimationRunning(running, l); } } @@ -2553,11 +2565,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override + public int getHeightWithoutLockscreenConstraints() { + mIgnoreLockscreenConstraints = true; + final int height = getIntrinsicHeight(); + mIgnoreLockscreenConstraints = false; + return height; + } + + @Override public int getIntrinsicHeight() { if (isUserLocked()) { return getActualHeight(); - } - if (mGuts != null && mGuts.isExposed()) { + } else if (mGuts != null && mGuts.isExposed()) { return mGuts.getIntrinsicHeight(); } else if ((isChildInGroup() && !isGroupExpanded())) { return mPrivateLayout.getMinHeight(); @@ -2579,13 +2598,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView return getCollapsedHeight(); } } - /** * @return {@code true} if the notification can show it's heads up layout. This is mostly true * except for legacy use cases. */ public boolean canShowHeadsUp() { - if (mOnKeyguard && !isDozing() && !isBypassEnabled() && !mEntry.isStickyAndNotDemoted()) { + if (mOnKeyguard && !isDozing() && !isBypassEnabled() && + (!mEntry.isStickyAndNotDemoted() + || (!mIgnoreLockscreenConstraints && mSaveSpaceOnLockscreen))) { return false; } return true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 9df6ba9910cc..5edff5f4e5d2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -291,6 +291,11 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable, Ro long duration) { } + public int getHeightWithoutLockscreenConstraints() { + // ExpandableNotificationRow overrides this. + return getHeight(); + } + /** * @return The desired notification height. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt new file mode 100644 index 000000000000..8ba65f7a1418 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractor.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.shelf.domain.interactor + +import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.statusbar.NotificationShelf +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine + +/** Interactor for the [NotificationShelf] */ +@CentralSurfacesComponent.CentralSurfacesScope +class NotificationShelfInteractor +@Inject +constructor( + private val keyguardRepository: KeyguardRepository, + private val deviceEntryFaceAuthRepository: DeviceEntryFaceAuthRepository, +) { + /** Is the shelf showing on the keyguard? */ + val isShowingOnKeyguard: Flow<Boolean> + get() = keyguardRepository.isKeyguardShowing + + /** Is the system in a state where the shelf is just a static display of notification icons? */ + val isShelfStatic: Flow<Boolean> + get() = + combine( + keyguardRepository.isKeyguardShowing, + deviceEntryFaceAuthRepository.isBypassEnabled, + ) { isKeyguardShowing, isBypassEnabled -> + isKeyguardShowing && isBypassEnabled + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt index a2351578ec98..b190cf658fa8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/view/NotificationShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt @@ -14,37 +14,35 @@ * limitations under the License. */ -package com.android.systemui.statusbar.notification.shelf.view +package com.android.systemui.statusbar.notification.shelf.ui.viewbinder import android.view.View -import android.view.View.OnAttachStateChangeListener import android.view.accessibility.AccessibilityManager +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.LegacyNotificationShelfControllerImpl import com.android.systemui.statusbar.NotificationShelf import com.android.systemui.statusbar.NotificationShelfController -import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.row.ActivatableNotificationView import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController import com.android.systemui.statusbar.notification.row.ExpandableOutlineViewController import com.android.systemui.statusbar.notification.row.ExpandableViewController +import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController -import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.phone.NotificationIconContainer import com.android.systemui.statusbar.phone.NotificationTapHelper import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope -import dagger.Binds -import dagger.Module +import com.android.systemui.util.kotlin.getValue +import dagger.Lazy import javax.inject.Inject - -/** Binds a [NotificationShelf] to its backend. */ -interface NotificationShelfViewBinder { - fun bind(shelf: NotificationShelf) -} +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach /** * Controller class for [NotificationShelf]. This implementation serves as a temporary wrapper @@ -57,30 +55,30 @@ class NotificationShelfViewBinderWrapperControllerImpl @Inject constructor( private val shelf: NotificationShelf, - private val viewBinder: NotificationShelfViewBinder, - private val keyguardBypassController: KeyguardBypassController, + private val viewModel: NotificationShelfViewModel, featureFlags: FeatureFlags, private val notifTapHelperFactory: NotificationTapHelper.Factory, private val a11yManager: AccessibilityManager, private val falsingManager: FalsingManager, private val falsingCollector: FalsingCollector, - private val statusBarStateController: SysuiStatusBarStateController, + hostControllerLazy: Lazy<NotificationStackScrollLayoutController>, ) : NotificationShelfController { - private var ambientState: AmbientState? = null + private val hostController: NotificationStackScrollLayoutController by hostControllerLazy override val view: NotificationShelf - get() = shelf + get() = unsupported init { shelf.apply { + setRefactorFlagEnabled(featureFlags.isEnabled(Flags.NOTIFICATION_SHELF_REFACTOR)) useRoundnessSourceTypes(featureFlags.isEnabled(Flags.USE_ROUNDNESS_SOURCETYPES)) setSensitiveRevealAnimEndabled(featureFlags.isEnabled(Flags.SENSITIVE_REVEAL_ANIM)) } } fun init() { - viewBinder.bind(shelf) + NotificationShelfViewBinder.bind(viewModel, shelf) ActivatableNotificationViewController( shelf, @@ -91,23 +89,9 @@ constructor( falsingCollector, ) .init() - shelf.setController(this) - val onAttachStateListener = - object : OnAttachStateChangeListener { - override fun onViewAttachedToWindow(v: View) { - statusBarStateController.addCallback( - shelf, - SysuiStatusBarStateController.RANK_SHELF, - ) - } - - override fun onViewDetachedFromWindow(v: View) { - statusBarStateController.removeCallback(shelf) - } - } - shelf.addOnAttachStateChangeListener(onAttachStateListener) - if (shelf.isAttachedToWindow) { - onAttachStateListener.onViewAttachedToWindow(shelf) + hostController.setShelf(shelf) + hostController.setOnNotificationRemovedListener { child, _ -> + view.requestRoundnessResetFor(child) } } @@ -117,10 +101,7 @@ constructor( override val shelfIcons: NotificationIconContainer get() = shelf.shelfIcons - override fun canModifyColorOfNotifications(): Boolean { - return (ambientState?.isShadeExpanded == true && - !(ambientState?.isOnKeyguard == true && keyguardBypassController.bypassEnabled)) - } + override fun canModifyColorOfNotifications(): Boolean = unsupported override fun setOnActivatedListener(listener: ActivatableNotificationView.OnActivatedListener) { shelf.setOnActivatedListener(listener) @@ -128,25 +109,27 @@ constructor( override fun bind( ambientState: AmbientState, - notificationStackScrollLayoutController: NotificationStackScrollLayoutController - ) { - shelf.bind(ambientState, notificationStackScrollLayoutController) - this.ambientState = ambientState - } + notificationStackScrollLayoutController: NotificationStackScrollLayoutController, + ) = unsupported override fun setOnClickListener(listener: View.OnClickListener) { shelf.setOnClickListener(listener) } -} -@Module(includes = [PrivateShelfViewBinderModule::class]) object NotificationShelfViewBinderModule - -@Module -private interface PrivateShelfViewBinderModule { - @Binds fun bindImpl(impl: NotificationShelfViewBinderImpl): NotificationShelfViewBinder + private val unsupported: Nothing + get() = NotificationShelfController.throwIllegalFlagStateError(expected = true) } -@CentralSurfacesScope -private class NotificationShelfViewBinderImpl @Inject constructor() : NotificationShelfViewBinder { - override fun bind(shelf: NotificationShelf) {} +/** Binds a [NotificationShelf] to its backend. */ +object NotificationShelfViewBinder { + fun bind(viewModel: NotificationShelfViewModel, shelf: NotificationShelf) { + shelf.repeatWhenAttached { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.canModifyColorOfNotifications + .onEach(shelf::setCanModifyColorOfNotifications) + .launchIn(this) + viewModel.isClickable.onEach(shelf::setCanInteract).launchIn(this) + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt new file mode 100644 index 000000000000..5e297c89dd2f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModel.kt @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.shelf.ui.viewmodel + +import com.android.systemui.statusbar.NotificationShelf +import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor +import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** ViewModel for [NotificationShelf]. */ +@CentralSurfacesScope +class NotificationShelfViewModel +@Inject +constructor( + private val interactor: NotificationShelfInteractor, +) { + /** Is the shelf allowed to be clickable when it has content? */ + val isClickable: Flow<Boolean> + get() = interactor.isShowingOnKeyguard + + /** Is the shelf allowed to modify the color of notifications in the host layout? */ + val canModifyColorOfNotifications: Flow<Boolean> + get() = interactor.isShelfStatic.map { static -> !static } +} 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/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index e47e4146d4c0..af608a7f3a64 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -5137,8 +5137,26 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable requestChildrenUpdate(); } + public void setShelf(NotificationShelf shelf) { + if (!NotificationShelfController.checkRefactorFlagEnabled( + mAmbientState.getFeatureFlags())) { + return; + } + int index = -1; + if (mShelf != null) { + index = indexOfChild(mShelf); + removeView(mShelf); + } + mShelf = shelf; + addView(mShelf, index); + mAmbientState.setShelf(mShelf); + mStateAnimator.setShelf(mShelf); + shelf.bind(mAmbientState, this, mController.getNotificationRoundnessManager()); + } + @ShadeViewRefactor(RefactorComponent.SHADE_VIEW) public void setShelfController(NotificationShelfController notificationShelfController) { + NotificationShelfController.assertRefactorFlagDisabled(mAmbientState.getFeatureFlags()); int index = -1; if (mShelf != null) { index = indexOfChild(mShelf); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 792746c60134..1c8727f1b092 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -73,6 +73,7 @@ import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener; import com.android.systemui.statusbar.NotificationRemoteInputManager; +import com.android.systemui.statusbar.NotificationShelf; import com.android.systemui.statusbar.NotificationShelfController; import com.android.systemui.statusbar.RemoteInputController; import com.android.systemui.statusbar.StatusBarState; @@ -1382,6 +1383,7 @@ public class NotificationStackScrollLayoutController { } public void setShelfController(NotificationShelfController notificationShelfController) { + NotificationShelfController.assertRefactorFlagDisabled(mFeatureFlags); mView.setShelfController(notificationShelfController); } @@ -1593,6 +1595,11 @@ public class NotificationStackScrollLayoutController { mView.setOnNotificationRemovedListener(listener); } + public void setShelf(NotificationShelf shelf) { + if (!NotificationShelfController.checkRefactorFlagEnabled(mFeatureFlags)) return; + mView.setShelf(shelf); + } + /** * Enum for UiEvent logged from this class */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index 092242814869..c7cb70c1da05 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -23,12 +23,14 @@ import androidx.annotation.VisibleForTesting import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow import com.android.systemui.statusbar.notification.row.ExpandableView import com.android.systemui.util.Compile +import com.android.systemui.util.LargeScreenUtils.shouldUseSplitNotificationShade import com.android.systemui.util.children import java.io.PrintWriter import javax.inject.Inject @@ -51,11 +53,10 @@ class NotificationStackSizeCalculator constructor( private val statusBarStateController: SysuiStatusBarStateController, private val lockscreenShadeTransitionController: LockscreenShadeTransitionController, + private val mediaDataManager: MediaDataManager, @Main private val resources: Resources ) { - private lateinit var lastComputeHeightLog : String - /** * Maximum # notifications to show on Keyguard; extras will be collapsed in an overflow shelf. * If there are exactly 1 + mMaxKeyguardNotifications, and they fit in the available space @@ -67,67 +68,157 @@ constructor( /** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */ private var dividerHeight by notNull<Float>() + /** + * True when there is not enough vertical space to show at least one notification with heads up + * layout. When true, notifications always show collapsed layout. + */ + private var saveSpaceOnLockscreen = false + init { updateResources() } /** * Returns whether notifications and (shelf if visible) can fit in total space available. - * [spaceForShelf] is extra vertical space allowed for the shelf to overlap the lock icon. + * [shelfSpace] is extra vertical space allowed for the shelf to overlap the lock icon. */ private fun canStackFitInSpace( stackHeight: StackHeight, - spaceForNotifications: Float, - spaceForShelf: Float, - ): Boolean { - - val (notificationsHeight, shelfHeightWithSpaceBefore) = stackHeight - var canFit: Boolean + notifSpace: Float, + shelfSpace: Float, + ): FitResult { + val (notifHeight, notifHeightSaveSpace, shelfHeightWithSpaceBefore) = stackHeight if (shelfHeightWithSpaceBefore == 0f) { - canFit = notificationsHeight <= spaceForNotifications + if (notifHeight <= notifSpace) { + log { + "\tcanStackFitInSpace[FIT] = notifHeight[$notifHeight]" + + " <= notifSpace[$notifSpace]" + } + return FitResult.FIT + } + if (notifHeightSaveSpace <= notifSpace) { + log { + "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" + + " = notifHeightSaveSpace[$notifHeightSaveSpace]" + + " <= notifSpace[$notifSpace]" + } + return FitResult.FIT_IF_SAVE_SPACE + } log { - "canStackFitInSpace[$canFit] = notificationsHeight[$notificationsHeight]" + - " <= spaceForNotifications[$spaceForNotifications]" + "\tcanStackFitInSpace[NO_FIT]" + + " = notifHeightSaveSpace[$notifHeightSaveSpace] > notifSpace[$notifSpace]" } + return FitResult.NO_FIT } else { - canFit = - (notificationsHeight + shelfHeightWithSpaceBefore) <= - (spaceForNotifications + spaceForShelf) - log { - "canStackFitInSpace[$canFit] = (notificationsHeight[$notificationsHeight]" + - " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" + - " <= (spaceForNotifications[$spaceForNotifications] " + - " + spaceForShelf[$spaceForShelf])" + if ((notifHeight + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace)) { + log { + "\tcanStackFitInSpace[FIT] = (notifHeight[$notifHeight]" + + " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" + + " <= (notifSpace[$notifSpace] " + + " + spaceForShelf[$shelfSpace])" + } + return FitResult.FIT + } else if ( + (notifHeightSaveSpace + shelfHeightWithSpaceBefore) <= (notifSpace + shelfSpace) + ) { + log { + "\tcanStackFitInSpace[FIT_IF_SAVE_SPACE]" + + " = (notifHeightSaveSpace[$notifHeightSaveSpace]" + + " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" + + " <= (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])" + } + return FitResult.FIT_IF_SAVE_SPACE + } else { + log { + "\tcanStackFitInSpace[NO_FIT]" + + " = (notifHeightSaveSpace[$notifHeightSaveSpace]" + + " + shelfHeightWithSpaceBefore[$shelfHeightWithSpaceBefore])" + + " > (notifSpace[$notifSpace] + shelfSpace[$shelfSpace])" + } + return FitResult.NO_FIT } } - return canFit } /** - * Given the [spaceForNotifications] and [spaceForShelf] constraints, calculate how many - * notifications to show. This number is only valid in keyguard. + * Given the [notifSpace] and [shelfSpace] constraints, calculate how many notifications to + * show. This number is only valid in keyguard. * * @param totalAvailableSpace space for notifications. This includes the space for the shelf. */ fun computeMaxKeyguardNotifications( stack: NotificationStackScrollLayout, - spaceForNotifications: Float, - spaceForShelf: Float, - shelfIntrinsicHeight: Float + notifSpace: Float, + shelfSpace: Float, + shelfHeight: Float, ): Int { + log { "\n " } + log { + "computeMaxKeyguardNotifications ---" + + "\n\tnotifSpace $notifSpace" + + "\n\tspaceForShelf $shelfSpace" + + "\n\tshelfIntrinsicHeight $shelfHeight" + } + if (notifSpace + shelfSpace <= 0f) { + log { "--- No space to show anything. maxNotifs=0" } + return 0 + } log { "\n" } - val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight, - /* computeHeight= */ false) + val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight) + val isMediaShowing = mediaDataManager.hasActiveMediaOrRecommendation() - var maxNotifications = + log { "\tGet maxNotifWithoutSavingSpace ---" } + val maxNotifWithoutSavingSpace = stackHeightSequence.lastIndexWhile { heightResult -> canStackFitInSpace( heightResult, - spaceForNotifications = spaceForNotifications, - spaceForShelf = spaceForShelf) + notifSpace = notifSpace, + shelfSpace = shelfSpace + ) == FitResult.FIT + } + + // How many notifications we can show at heightWithoutLockscreenConstraints + var minCountAtHeightWithoutConstraints = + if (isMediaShowing && !shouldUseSplitNotificationShade(resources)) 2 else 1 + log { + "\t---maxNotifWithoutSavingSpace=$maxNotifWithoutSavingSpace " + + "isMediaShowing=$isMediaShowing" + + "minCountAtHeightWithoutConstraints=$minCountAtHeightWithoutConstraints" + } + log { "\n" } + + var maxNotifications: Int + if (maxNotifWithoutSavingSpace >= minCountAtHeightWithoutConstraints) { + saveSpaceOnLockscreen = false + maxNotifications = maxNotifWithoutSavingSpace + log { + "\tDo NOT save space. maxNotifications=maxNotifWithoutSavingSpace=$maxNotifications" + } + } else { + log { "\tSAVE space ---" } + saveSpaceOnLockscreen = true + maxNotifications = + stackHeightSequence.lastIndexWhile { heightResult -> + canStackFitInSpace( + heightResult, + notifSpace = notifSpace, + shelfSpace = shelfSpace + ) != FitResult.NO_FIT + } + log { "\t--- maxNotifications=$maxNotifications" } + } + + // Must update views immediately to avoid mismatches between initial HUN layout height + // and the height adapted to lockscreen space constraints, which causes jump cuts. + stack.showableChildren().toList().forEach { currentNotification -> + run { + if (currentNotification is ExpandableNotificationRow) { + currentNotification.saveSpaceOnLockscreen = saveSpaceOnLockscreen + } } + } if (onLockscreen()) { maxNotifications = min(maxKeyguardNotifications, maxNotifications) @@ -137,53 +228,80 @@ constructor( maxNotifications = max(0, maxNotifications) log { val sequence = if (SPEW) " stackHeightSequence=${stackHeightSequence.toList()}" else "" - "computeMaxKeyguardNotifications(" + - " spaceForNotifications=$spaceForNotifications" + - " spaceForShelf=$spaceForShelf" + - " shelfHeight=$shelfIntrinsicHeight) -> $maxNotifications$sequence" + "--- computeMaxKeyguardNotifications(" + + " notifSpace=$notifSpace" + + " shelfSpace=$shelfSpace" + + " shelfHeight=$shelfHeight) -> $maxNotifications$sequence" } + log { "\n" } return maxNotifications } /** - * Given the [maxNotifications] constraint, calculates the height of the + * Given the [maxNotifs] constraint, calculates the height of the * [NotificationStackScrollLayout]. This might or might not be in keyguard. * * @param stack stack containing notifications as children. - * @param maxNotifications Maximum number of notifications. When reached, the others will go - * into the shelf. - * @param shelfIntrinsicHeight height of the shelf, without any padding. It might be zero. - * + * @param maxNotifs Maximum number of notifications. When reached, the others will go into the + * shelf. + * @param shelfHeight height of the shelf, without any padding. It might be zero. * @return height of the stack, including shelf height, if needed. */ fun computeHeight( stack: NotificationStackScrollLayout, - maxNotifications: Int, - shelfIntrinsicHeight: Float + maxNotifs: Int, + shelfHeight: Float ): Float { log { "\n" } - lastComputeHeightLog = "" - val heightPerMaxNotifications = - computeHeightPerNotificationLimit(stack, shelfIntrinsicHeight, - /* computeHeight= */ true) - - val (notificationsHeight, shelfHeightWithSpaceBefore) = - heightPerMaxNotifications.elementAtOrElse(maxNotifications) { - heightPerMaxNotifications.last() // Height with all notifications visible. + log { "computeHeight ---" } + + val stackHeightSequence = computeHeightPerNotificationLimit(stack, shelfHeight) + + val (notifsHeight, notifsHeightSavingSpace, shelfHeightWithSpaceBefore) = + stackHeightSequence.elementAtOrElse(maxNotifs) { + stackHeightSequence.last() // Height with all notifications visible. + } + + var height: Float + if (saveSpaceOnLockscreen) { + height = notifsHeightSavingSpace + shelfHeightWithSpaceBefore + log { + "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" + + " -> $height=($notifsHeightSavingSpace+$shelfHeightWithSpaceBefore)," + + " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen" + } + } else { + height = notifsHeight + shelfHeightWithSpaceBefore + log { + "--- computeHeight(maxNotifs=$maxNotifs, shelfHeight=$shelfHeight)" + + " -> ${height}=($notifsHeight+$shelfHeightWithSpaceBefore)" + + " | saveSpaceOnLockscreen=$saveSpaceOnLockscreen" } - lastComputeHeightLog += "\ncomputeHeight(maxNotifications=$maxNotifications," + - "shelfIntrinsicHeight=$shelfIntrinsicHeight) -> " + - "${notificationsHeight + shelfHeightWithSpaceBefore}" + - " = ($notificationsHeight + $shelfHeightWithSpaceBefore)" - log { - lastComputeHeightLog } - return notificationsHeight + shelfHeightWithSpaceBefore + return height } + private enum class FitResult { + FIT, + FIT_IF_SAVE_SPACE, + NO_FIT + } + + data class SpaceNeeded( + // Float height of spaceNeeded when showing heads up layout for FSI HUNs. + val whenEnoughSpace: Float, + + // Float height of space needed when showing collapsed layout for FSI HUNs. + val whenSavingSpace: Float + ) + private data class StackHeight( // Float height with ith max notifications (not including shelf) - val notificationsHeight: Float, + val notifsHeight: Float, + + // Float height with ith max notifications + // (not including shelf, using collapsed layout for FSI HUN) + val notifsHeightSavingSpace: Float, // Float height of shelf (0 if shelf is not showing), and space before the shelf that // changes during the lockscreen <=> full shade transition. @@ -193,20 +311,27 @@ constructor( private fun computeHeightPerNotificationLimit( stack: NotificationStackScrollLayout, shelfHeight: Float, - computeHeight: Boolean ): Sequence<StackHeight> = sequence { - log { "computeHeightPerNotificationLimit" } - val children = stack.showableChildren().toList() var notifications = 0f + var notifsWithCollapsedHun = 0f var previous: ExpandableView? = null val onLockscreen = onLockscreen() // Only shelf. This should never happen, since we allow 1 view minimum (EmptyViewState). - yield(StackHeight(notificationsHeight = 0f, shelfHeightWithSpaceBefore = shelfHeight)) + yield( + StackHeight( + notifsHeight = 0f, + notifsHeightSavingSpace = 0f, + shelfHeightWithSpaceBefore = shelfHeight + ) + ) children.forEachIndexed { i, currentNotification -> - notifications += spaceNeeded(currentNotification, i, previous, stack, onLockscreen) + val space = getSpaceNeeded(currentNotification, i, previous, stack, onLockscreen) + notifications += space.whenEnoughSpace + notifsWithCollapsedHun += space.whenSavingSpace + previous = currentNotification val shelfWithSpaceBefore = @@ -219,22 +344,23 @@ constructor( stack, previous = currentNotification, current = children[firstViewInShelfIndex], - currentIndex = firstViewInShelfIndex) + currentIndex = firstViewInShelfIndex + ) spaceBeforeShelf + shelfHeight } - val currentLog = "computeHeight | i=$i notificationsHeight=$notifications " + - "shelfHeightWithSpaceBefore=$shelfWithSpaceBefore" - if (computeHeight) { - lastComputeHeightLog += "\n" + currentLog - } log { - currentLog + "\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " + + "notifsHeightSavingSpace=$notifsWithCollapsedHun" + + " shelfWithSpaceBefore=$shelfWithSpaceBefore" } yield( StackHeight( - notificationsHeight = notifications, - shelfHeightWithSpaceBefore = shelfWithSpaceBefore)) + notifsHeight = notifications, + notifsHeightSavingSpace = notifsWithCollapsedHun, + shelfHeightWithSpaceBefore = shelfWithSpaceBefore + ) + ) } } @@ -256,32 +382,46 @@ constructor( } @VisibleForTesting - fun spaceNeeded( + fun getSpaceNeeded( view: ExpandableView, visibleIndex: Int, previousView: ExpandableView?, stack: NotificationStackScrollLayout, - onLockscreen: Boolean - ): Float { + onLockscreen: Boolean, + ): SpaceNeeded { assert(view.isShowable(onLockscreen)) + // Must use heightWithoutLockscreenConstraints because intrinsicHeight references + // mSaveSpaceOnLockscreen and using intrinsicHeight here will result in stack overflow. + val height = view.heightWithoutLockscreenConstraints.toFloat() + val gapAndDividerHeight = + calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex) + var size = if (onLockscreen) { if (view is ExpandableNotificationRow && view.entry.isStickyAndNotDemoted) { - view.intrinsicHeight.toFloat() + height } else { view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat() } } else { - view.intrinsicHeight.toFloat() + height + } + size += gapAndDividerHeight + + var sizeWhenSavingSpace = + if (onLockscreen) { + view.getMinHeight(/* ignoreTemporaryStates= */ true).toFloat() + } else { + height } + sizeWhenSavingSpace += gapAndDividerHeight - size += calculateGapAndDividerHeight(stack, previousView, current = view, visibleIndex) - return size + return SpaceNeeded(size, sizeWhenSavingSpace) } fun dump(pw: PrintWriter, args: Array<out String>) { - pw.println("NotificationStackSizeCalculator lastComputeHeightLog = $lastComputeHeightLog") + pw.println("NotificationStackSizeCalculator saveSpaceOnLockscreen=$saveSpaceOnLockscreen") } private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean { 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/KeyguardPreviewContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java deleted file mode 100644 index 076e5f1c1ce7..000000000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardPreviewContainer.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package com.android.systemui.statusbar.phone; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.view.WindowInsets; -import android.widget.FrameLayout; - -/** - * A view group which contains the preview of phone/camera and draws a black bar at the bottom as - * the fake navigation bar. - */ -public class KeyguardPreviewContainer extends FrameLayout { - - private Drawable mBlackBarDrawable = new Drawable() { - @Override - public void draw(Canvas canvas) { - canvas.save(); - canvas.clipRect(0, getHeight() - getPaddingBottom(), getWidth(), getHeight()); - canvas.drawColor(Color.BLACK); - canvas.restore(); - } - - @Override - public void setAlpha(int alpha) { - // noop - } - - @Override - public void setColorFilter(ColorFilter colorFilter) { - // noop - } - - @Override - public int getOpacity() { - return android.graphics.PixelFormat.OPAQUE; - } - }; - - public KeyguardPreviewContainer(Context context, AttributeSet attrs) { - super(context, attrs); - setBackground(mBlackBarDrawable); - } - - @Override - public WindowInsets onApplyWindowInsets(WindowInsets insets) { - setPadding(0, 0, 0, insets.getStableInsetBottom()); - return super.onApplyWindowInsets(insets); - } -} 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..d496cfaa0f03 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -69,6 +69,7 @@ import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.TaskbarDelegate; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionChangeEvent; @@ -285,8 +286,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final boolean mUdfpsNewTouchDetectionEnabled; private final UdfpsOverlayInteractor mUdfpsOverlayInteractor; - private OnDismissAction mAfterKeyguardGoneAction; - private Runnable mKeyguardGoneCancelAction; + @VisibleForTesting + OnDismissAction mAfterKeyguardGoneAction; + + @VisibleForTesting + Runnable mKeyguardGoneCancelAction; private boolean mDismissActionWillAnimateOnKeyguard; private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>(); @@ -302,6 +306,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb @Nullable private KeyguardBypassController mBypassController; @Nullable private OccludingAppBiometricUI mOccludingAppBiometricUI; + private UserTracker mUserTracker; + @Nullable private TaskbarDelegate mTaskbarDelegate; private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = new KeyguardUpdateMonitorCallback() { @@ -339,7 +345,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb PrimaryBouncerInteractor primaryBouncerInteractor, BouncerView primaryBouncerView, AlternateBouncerInteractor alternateBouncerInteractor, - UdfpsOverlayInteractor udfpsOverlayInteractor + UdfpsOverlayInteractor udfpsOverlayInteractor, + UserTracker userTracker ) { mContext = context; mViewMediatorCallback = callback; @@ -367,6 +374,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM); mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION); mUdfpsOverlayInteractor = udfpsOverlayInteractor; + mUserTracker = userTracker; } @Override @@ -662,14 +670,21 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb if (afterKeyguardGone) { // we'll handle the dismiss action after keyguard is gone, so just show the // bouncer - mPrimaryBouncerInteractor.show(/* isScrimmed= */true); + if (mLockPatternUtils.isSecure(mUserTracker.getUserId())) { + mPrimaryBouncerInteractor.show(true); + } } else { - // after authentication success, run dismiss action with the option to defer - // hiding the keyguard based on the return value of the OnDismissAction - mPrimaryBouncerInteractor.setDismissAction( - mAfterKeyguardGoneAction, mKeyguardGoneCancelAction); - mPrimaryBouncerInteractor.show(/* isScrimmed= */true); - // bouncer will handle the dismiss action, so we no longer need to track it here + if (mLockPatternUtils.isSecure(mUserTracker.getUserId())) { + // after authentication success, run dismiss action with the option to defer + // hiding the keyguard based on the return value of the OnDismissAction + mPrimaryBouncerInteractor.setDismissAction( + mAfterKeyguardGoneAction, mKeyguardGoneCancelAction); + mPrimaryBouncerInteractor.show(true); + } else { + if (mAfterKeyguardGoneAction != null) { + mAfterKeyguardGoneAction.onDismiss(); + } + } mAfterKeyguardGoneAction = null; mKeyguardGoneCancelAction = null; } 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/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java index cc2a0ba6f798..5d4addab240a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java @@ -52,8 +52,7 @@ import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent; -import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderModule; -import com.android.systemui.statusbar.notification.shelf.view.NotificationShelfViewBinderWrapperControllerImpl; +import com.android.systemui.statusbar.notification.shelf.ui.viewbinder.NotificationShelfViewBinderWrapperControllerImpl; import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.phone.KeyguardBottomAreaView; import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator; @@ -87,8 +86,7 @@ import dagger.Module; import dagger.Provides; import dagger.multibindings.IntoSet; -@Module(subcomponents = StatusBarFragmentComponent.class, - includes = { NotificationShelfViewBinderModule.class }) +@Module(subcomponents = StatusBarFragmentComponent.class) public abstract class StatusBarViewModule { public static final String SHADE_HEADER = "large_screen_shade_header"; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt index 075e6ec11ae7..a05ab849088d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt @@ -26,15 +26,7 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsVi import java.io.PrintWriter import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.mapLatest -import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** @@ -46,39 +38,16 @@ import kotlinx.coroutines.launch * the list of available mobile lines of service for which we want to show icons. */ @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") -@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class MobileUiAdapter @Inject constructor( - interactor: MobileIconsInteractor, private val iconController: StatusBarIconController, - private val iconsViewModelFactory: MobileIconsViewModel.Factory, + val mobileIconsViewModel: MobileIconsViewModel, private val logger: MobileViewLogger, @Application private val scope: CoroutineScope, private val statusBarPipelineFlags: StatusBarPipelineFlags, ) : CoreStartable { - private val mobileSubIds: Flow<List<Int>> = - interactor.filteredSubscriptions.mapLatest { subscriptions -> - subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } - } - - /** - * We expose the list of tracked subscriptions as a flow of a list of ints, where each int is - * the subscriptionId of the relevant subscriptions. These act as a key into the layouts which - * house the mobile infos. - * - * NOTE: this should go away as the view presenter learns more about this data pipeline - */ - private val mobileSubIdsState: StateFlow<List<Int>> = - mobileSubIds - .distinctUntilChanged() - .onEach { logger.logUiAdapterSubIdsUpdated(it) } - .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) - - /** In order to keep the logs tame, we will reuse the same top-level mobile icons view model */ - val mobileIconsViewModel = iconsViewModelFactory.create(mobileSubIdsState) - private var isCollecting: Boolean = false private var lastValue: List<Int>? = null @@ -90,7 +59,7 @@ constructor( if (statusBarPipelineFlags.useNewMobileIcons()) { scope.launch { isCollecting = true - mobileSubIds.collectLatest { + mobileIconsViewModel.subscriptionIdsFlow.collectLatest { logger.logUiAdapterSubIdsSentToIconController(it) lastValue = it iconController.setNewMobileIconSubIds(it) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt index 90dff23c637c..f2f91430eba6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileViewLogger.kt @@ -41,15 +41,6 @@ constructor( private val collectionStatuses = mutableMapOf<String, Boolean>() - fun logUiAdapterSubIdsUpdated(subs: List<Int>) { - buffer.log( - TAG, - LogLevel.INFO, - { str1 = subs.toString() }, - { "Sub IDs in MobileUiAdapter updated internally: $str1" }, - ) - } - fun logUiAdapterSubIdsSentToIconController(subs: List<Int>) { buffer.log( TAG, 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 2b90065284d0..9af5e836659f 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 @@ -29,18 +29,23 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMob import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.mapLatest +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch /** * View model for describing the system's current mobile cellular connections. The result is a list * of [MobileIconViewModel]s which describe the individual icons and can be bound to - * [ModernStatusBarMobileView] + * [ModernStatusBarMobileView]. */ +@OptIn(ExperimentalCoroutinesApi::class) +@SysUISingleton class MobileIconsViewModel @Inject constructor( - val subscriptionIdsFlow: StateFlow<List<Int>>, val logger: MobileViewLogger, private val verboseLogger: VerboseMobileViewLogger, private val interactor: MobileIconsInteractor, @@ -51,6 +56,13 @@ constructor( ) { @VisibleForTesting val mobileIconSubIdCache = mutableMapOf<Int, MobileIconViewModel>() + val subscriptionIdsFlow: StateFlow<List<Int>> = + interactor.filteredSubscriptions + .mapLatest { subscriptions -> + subscriptions.map { subscriptionModel -> subscriptionModel.subscriptionId } + } + .stateIn(scope, SharingStarted.WhileSubscribed(), listOf()) + init { scope.launch { subscriptionIdsFlow.collect { removeInvalidModelsFromCache(it) } } } @@ -79,30 +91,4 @@ constructor( val subIdsToRemove = mobileIconSubIdCache.keys.filter { !subIds.contains(it) } subIdsToRemove.forEach { mobileIconSubIdCache.remove(it) } } - - @SysUISingleton - class Factory - @Inject - constructor( - private val logger: MobileViewLogger, - private val verboseLogger: VerboseMobileViewLogger, - private val interactor: MobileIconsInteractor, - private val airplaneModeInteractor: AirplaneModeInteractor, - private val constants: ConnectivityConstants, - @Application private val scope: CoroutineScope, - private val statusBarPipelineFlags: StatusBarPipelineFlags, - ) { - fun create(subscriptionIdsFlow: StateFlow<List<Int>>): MobileIconsViewModel { - return MobileIconsViewModel( - subscriptionIdsFlow, - logger, - verboseLogger, - interactor, - airplaneModeInteractor, - constants, - scope, - statusBarPipelineFlags, - ) - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java index 654ba04eba7a..1e63b2a4b3e1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java @@ -21,6 +21,8 @@ import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN; import static android.os.BatteryManager.EXTRA_HEALTH; import static android.os.BatteryManager.EXTRA_PRESENT; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS; + import android.annotation.WorkerThread; import android.content.BroadcastReceiver; import android.content.Context; @@ -169,7 +171,8 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC @Override public void setPowerSaveMode(boolean powerSave, View view) { if (powerSave) mPowerSaverStartView.set(new WeakReference<>(view)); - BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true); + BatterySaverUtils.setPowerSaveMode(mContext, powerSave, /*needFirstTimeWarning*/ true, + SAVER_ENABLED_QS); } @Override 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/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt index a20a5b2fdbbc..e819f946a6d6 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt @@ -31,6 +31,7 @@ import android.view.WindowManager import android.view.accessibility.AccessibilityManager import android.widget.ImageView import android.widget.TextView +import androidx.annotation.DimenRes import androidx.annotation.IdRes import androidx.annotation.VisibleForTesting import com.android.internal.widget.CachingIconView @@ -180,8 +181,9 @@ constructor( // Button val buttonView = currentView.requireViewById<TextView>(R.id.end_button) - if (newInfo.endItem is ChipbarEndItem.Button) { - TextViewBinder.bind(buttonView, newInfo.endItem.text) + val hasButton = newInfo.endItem is ChipbarEndItem.Button + if (hasButton) { + TextViewBinder.bind(buttonView, (newInfo.endItem as ChipbarEndItem.Button).text) val onClickListener = View.OnClickListener { clickedView -> @@ -196,6 +198,12 @@ constructor( buttonView.visibility = View.GONE } + currentView + .getInnerView() + .setEndPadding( + if (hasButton) R.dimen.chipbar_outer_padding_half else R.dimen.chipbar_outer_padding + ) + // ---- Overall accessibility ---- val iconDesc = newInfo.startIcon.icon.contentDescription val loadedIconDesc = @@ -309,6 +317,15 @@ constructor( viewUtil.setRectToViewWindowLocation(view, outRect) } + private fun View.setEndPadding(@DimenRes endPaddingDimen: Int) { + this.setPaddingRelative( + this.paddingStart, + this.paddingTop, + context.resources.getDimensionPixelSize(endPaddingDimen), + this.paddingBottom, + ) + } + private fun Boolean.visibleIfTrue(): Int { return if (this) { View.VISIBLE diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt index 6e58f2296c86..52f2d11f814e 100644 --- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt +++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt @@ -18,7 +18,7 @@ package com.android.systemui.temporarydisplay.chipbar import android.os.VibrationEffect import android.view.View -import androidx.annotation.ColorRes +import androidx.annotation.AttrRes import com.android.systemui.R import com.android.systemui.common.shared.model.Text import com.android.systemui.common.shared.model.TintedIcon @@ -49,7 +49,9 @@ data class ChipbarInfo( override val priority: ViewPriority, ) : TemporaryViewInfo() { companion object { - @ColorRes val DEFAULT_ICON_TINT = R.color.chipbar_text_and_icon_color + // LINT.IfChange + @AttrRes val DEFAULT_ICON_TINT = com.android.internal.R.attr.materialColorOnSecondaryFixed + // LINT.ThenChange(systemui/res/layout/chipbar.xml) } } 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/util/kotlin/Dagger.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.kt new file mode 100644 index 000000000000..c587f2edd601 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Dagger.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.util.kotlin + +import dagger.Lazy +import kotlin.reflect.KProperty + +/** + * Extension operator that allows developers to use [dagger.Lazy] as a property delegate: + * ```kotlin + * class MyClass @Inject constructor( + * lazyDependency: dagger.Lazy<Foo>, + * ) { + * val dependency: Foo by lazyDependency + * } + * ``` + */ +operator fun <T> Lazy<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get() diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java index 209ea41fed61..58cffa761a66 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java @@ -58,6 +58,7 @@ import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -418,6 +419,7 @@ public class GarbageMonitor implements Dumpable { @Inject public MemoryTile( QSHost host, + QsEventLogger uiEventLogger, @Background Looper backgroundLooper, @Main Handler mainHandler, FalsingManager falsingManager, @@ -428,7 +430,7 @@ public class GarbageMonitor implements Dumpable { GarbageMonitor monitor, PanelInteractor panelInteractor ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); gm = monitor; mPanelInteractor = panelInteractor; diff --git a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java index 35af44442ca9..e3ed2b405fb0 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java +++ b/packages/SystemUI/src/com/android/systemui/volume/CsdWarningDialog.java @@ -16,20 +16,34 @@ package com.android.systemui.volume; +import static android.app.PendingIntent.FLAG_IMMUTABLE; + import android.annotation.StringRes; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioManager; -import android.os.CountDownTimer; +import android.provider.Settings; import android.util.Log; import android.view.KeyEvent; import android.view.WindowManager; import com.android.internal.annotations.GuardedBy; +import com.android.internal.messages.nano.SystemMessageProto; +import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.statusbar.phone.SystemUIDialog; +import com.android.systemui.util.NotificationChannels; +import com.android.systemui.util.concurrency.DelayableExecutor; + +import dagger.assisted.Assisted; +import dagger.assisted.AssistedFactory; +import dagger.assisted.AssistedInject; /** * A class that implements the four Computed Sound Dose-related warnings defined in {@link AudioManager}: @@ -53,34 +67,58 @@ import com.android.systemui.statusbar.phone.SystemUIDialog; * communication between the native audio framework that implements the dose computation and the * audio service. */ -public abstract class CsdWarningDialog extends SystemUIDialog +public class CsdWarningDialog extends SystemUIDialog implements DialogInterface.OnDismissListener, DialogInterface.OnClickListener { private static final String TAG = Util.logTag(CsdWarningDialog.class); private static final int KEY_CONFIRM_ALLOWED_AFTER_MS = 1000; // milliseconds // time after which action is taken when the user hasn't ack'd or dismissed the dialog - private static final int NO_ACTION_TIMEOUT_MS = 5000; + public static final int NO_ACTION_TIMEOUT_MS = 5000; private final Context mContext; private final AudioManager mAudioManager; private final @AudioManager.CsdWarning int mCsdWarning; private final Object mTimerLock = new Object(); + /** * Timer to keep track of how long the user has before an action (here volume reduction) is * taken on their behalf. */ @GuardedBy("mTimerLock") - private final CountDownTimer mNoUserActionTimer; + private Runnable mNoUserActionRunnable; + private Runnable mCancelScheduledNoUserActionRunnable = null; + + private final DelayableExecutor mDelayableExecutor; + private NotificationManager mNotificationManager; + private Runnable mOnCleanup; private long mShowTime; - public CsdWarningDialog(@AudioManager.CsdWarning int csdWarning, Context context, - AudioManager audioManager) { + /** + * To inject dependencies and allow for easier testing + */ + @AssistedFactory + public interface Factory { + /** + * Create a dialog object + */ + CsdWarningDialog create(int csdWarning, Runnable onCleanup); + } + + @AssistedInject + public CsdWarningDialog(@Assisted @AudioManager.CsdWarning int csdWarning, Context context, + AudioManager audioManager, NotificationManager notificationManager, + @Background DelayableExecutor delayableExecutor, @Assisted Runnable onCleanup) { super(context); mCsdWarning = csdWarning; mContext = context; mAudioManager = audioManager; + mNotificationManager = notificationManager; + mOnCleanup = onCleanup; + + mDelayableExecutor = delayableExecutor; + getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); setShowForAllUsers(true); setMessage(mContext.getString(getStringForWarning(csdWarning))); @@ -95,25 +133,24 @@ public abstract class CsdWarningDialog extends SystemUIDialog Context.RECEIVER_EXPORTED_UNAUDITED); if (csdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) { - mNoUserActionTimer = new CountDownTimer(NO_ACTION_TIMEOUT_MS, NO_ACTION_TIMEOUT_MS) { - @Override - public void onTick(long millisUntilFinished) { } - - @Override - public void onFinish() { - if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) { - // unlike on the 5x dose repeat, level is only reduced to RS1 - // when the warning is not acknowledged quick enough - mAudioManager.lowerVolumeToRs1(); - } + mNoUserActionRunnable = () -> { + if (mCsdWarning == AudioManager.CSD_WARNING_DOSE_REACHED_1X) { + // unlike on the 5x dose repeat, level is only reduced to RS1 when the warning + // is not acknowledged quickly enough + mAudioManager.lowerVolumeToRs1(); + sendNotification(); } }; } else { - mNoUserActionTimer = null; + mNoUserActionRunnable = null; } } - protected abstract void cleanUp(); + private void cleanUp() { + if (mOnCleanup != null) { + mOnCleanup.run(); + } + } // NOT overriding onKeyDown as we're not allowing a dismissal on any key other than // VOLUME_DOWN, and for this, we don't need to track if it's the start of a new @@ -153,12 +190,9 @@ public abstract class CsdWarningDialog extends SystemUIDialog super.onStart(); mShowTime = System.currentTimeMillis(); synchronized (mTimerLock) { - if (mNoUserActionTimer != null) { - new Thread(() -> { - synchronized (mTimerLock) { - mNoUserActionTimer.start(); - } - }).start(); + if (mNoUserActionRunnable != null) { + mCancelScheduledNoUserActionRunnable = mDelayableExecutor.executeDelayed( + mNoUserActionRunnable, NO_ACTION_TIMEOUT_MS); } } } @@ -166,8 +200,8 @@ public abstract class CsdWarningDialog extends SystemUIDialog @Override protected void onStop() { synchronized (mTimerLock) { - if (mNoUserActionTimer != null) { - mNoUserActionTimer.cancel(); + if (mCancelScheduledNoUserActionRunnable != null) { + mCancelScheduledNoUserActionRunnable.run(); } } } @@ -212,4 +246,32 @@ public abstract class CsdWarningDialog extends SystemUIDialog Log.e(TAG, "Invalid CSD warning event " + csdWarning, new Exception()); return com.android.internal.R.string.csd_dose_reached_warning; } + + + /** + * In case user did not respond to the dialog, they still need to know volume was lowered. + */ + private void sendNotification() { + Intent intent = new Intent(Settings.ACTION_SOUND_SETTINGS); + PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, + FLAG_IMMUTABLE); + + String text = mContext.getString(R.string.csd_system_lowered_text); + String title = mContext.getString(R.string.csd_lowered_title); + + Notification.Builder builder = + new Notification.Builder(mContext, NotificationChannels.ALERTS) + .setSmallIcon(R.drawable.hearing) + .setContentTitle(title) + .setContentText(text) + .setContentIntent(pendingIntent) + .setStyle(new Notification.BigTextStyle().bigText(text)) + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setLocalOnly(true) + .setAutoCancel(true) + .setCategory(Notification.CATEGORY_SYSTEM); + + mNotificationManager.notify(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO, + builder.build()); + } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java index 95cc12a48bb2..3c007f99a654 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java @@ -263,6 +263,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, private final ConfigurationController mConfigurationController; private final MediaOutputDialogFactory mMediaOutputDialogFactory; private final VolumePanelFactory mVolumePanelFactory; + private final CsdWarningDialog.Factory mCsdWarningDialogFactory; private final ActivityStarter mActivityStarter; private boolean mShowing; @@ -311,6 +312,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, InteractionJankMonitor interactionJankMonitor, DeviceConfigProxy deviceConfigProxy, Executor executor, + CsdWarningDialog.Factory csdWarningDialogFactory, DumpManager dumpManager) { mContext = new ContextThemeWrapper(context, R.style.volume_dialog_theme); @@ -322,6 +324,7 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, mConfigurationController = configurationController; mMediaOutputDialogFactory = mediaOutputDialogFactory; mVolumePanelFactory = volumePanelFactory; + mCsdWarningDialogFactory = csdWarningDialogFactory; mActivityStarter = activityStarter; mShowActiveStreamOnly = showActiveStreamOnly(); mHasSeenODICaptionsTooltip = @@ -2084,21 +2087,21 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable, rescheduleTimeoutH(); } - private void showCsdWarningH(int csdWarning, int durationMs) { + @VisibleForTesting void showCsdWarningH(int csdWarning, int durationMs) { synchronized (mSafetyWarningLock) { + if (mCsdDialog != null) { return; } - mCsdDialog = new CsdWarningDialog(csdWarning, - mContext, mController.getAudioManager()) { - @Override - protected void cleanUp() { - synchronized (mSafetyWarningLock) { - mCsdDialog = null; - } - recheckH(null); + + final Runnable cleanUp = () -> { + synchronized (mSafetyWarningLock) { + mCsdDialog = null; } + recheckH(null); }; + + mCsdDialog = mCsdWarningDialogFactory.create(csdWarning, cleanUp); mCsdDialog.show(); } recheckH(null); diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java index 0ab6c690e1e1..14d3ca334073 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java +++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/VolumeModule.java @@ -30,17 +30,18 @@ import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.DeviceProvisionedController; import com.android.systemui.util.DeviceConfigProxy; +import com.android.systemui.volume.CsdWarningDialog; import com.android.systemui.volume.VolumeComponent; import com.android.systemui.volume.VolumeDialogComponent; import com.android.systemui.volume.VolumeDialogImpl; import com.android.systemui.volume.VolumePanelFactory; -import java.util.concurrent.Executor; - import dagger.Binds; import dagger.Module; import dagger.Provides; +import java.util.concurrent.Executor; + /** Dagger Module for code in the volume package. */ @Module @@ -63,6 +64,7 @@ public interface VolumeModule { InteractionJankMonitor interactionJankMonitor, DeviceConfigProxy deviceConfigProxy, @Main Executor executor, + CsdWarningDialog.Factory csdFactory, DumpManager dumpManager) { VolumeDialogImpl impl = new VolumeDialogImpl( context, @@ -76,6 +78,7 @@ public interface VolumeModule { interactionJankMonitor, deviceConfigProxy, executor, + csdFactory, dumpManager); impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false); impl.setAutomute(true); diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java deleted file mode 100644 index e93e86291535..000000000000 --- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRule2.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright (C) 2023 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package androidx.core.animation; - -import android.os.Looper; -import android.os.SystemClock; -import android.util.AndroidRuntimeException; - -import androidx.annotation.NonNull; - -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - -import java.util.ArrayList; -import java.util.List; - -/** - * NOTE: this is a copy of the {@link androidx.core.animation.AnimatorTestRule} which attempts to - * circumvent the problems with {@link androidx.core.animation.AnimationHandler} having a static - * list of callbacks. - * - * TODO(b/275602127): remove this and use the original rule once we have the updated androidx code. - */ -public final class AnimatorTestRule2 implements TestRule { - - class TestAnimationHandler extends AnimationHandler { - TestAnimationHandler() { - super(new TestProvider()); - } - - List<AnimationFrameCallback> animationCallbacks = new ArrayList<>(); - - @Override - void addAnimationFrameCallback(AnimationFrameCallback callback) { - animationCallbacks.add(callback); - callback.doAnimationFrame(getCurrentTime()); - } - - @Override - public void removeCallback(AnimationFrameCallback callback) { - int id = animationCallbacks.indexOf(callback); - if (id >= 0) { - animationCallbacks.set(id, null); - } - } - - void onAnimationFrame(long frameTime) { - for (int i = 0; i < animationCallbacks.size(); i++) { - final AnimationFrameCallback callback = animationCallbacks.get(i); - if (callback == null) { - continue; - } - callback.doAnimationFrame(frameTime); - } - } - - @Override - void autoCancelBasedOn(ObjectAnimator objectAnimator) { - for (int i = animationCallbacks.size() - 1; i >= 0; i--) { - AnimationFrameCallback cb = animationCallbacks.get(i); - if (cb == null) { - continue; - } - if (objectAnimator.shouldAutoCancel(cb)) { - ((Animator) animationCallbacks.get(i)).cancel(); - } - } - } - } - - final TestAnimationHandler mTestHandler; - final long mStartTime; - private long mTotalTimeDelta = 0; - private final Object mLock = new Object(); - - public AnimatorTestRule2() { - mStartTime = SystemClock.uptimeMillis(); - mTestHandler = new TestAnimationHandler(); - } - - @NonNull - @Override - public Statement apply(@NonNull final Statement base, @NonNull Description description) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - AnimationHandler.setTestHandler(mTestHandler); - try { - base.evaluate(); - } finally { - AnimationHandler.setTestHandler(null); - } - } - }; - } - - /** - * Advances the animation clock by the given amount of delta in milliseconds. This call will - * produce an animation frame to all the ongoing animations. This method needs to be - * called on the same thread as {@link Animator#start()}. - * - * @param timeDelta the amount of milliseconds to advance - */ - public void advanceTimeBy(long timeDelta) { - if (Looper.myLooper() == null) { - // Throw an exception - throw new AndroidRuntimeException("AnimationTestRule#advanceTimeBy(long) may only be" - + "called on Looper threads"); - } - synchronized (mLock) { - // Advance time & pulse a frame - mTotalTimeDelta += timeDelta < 0 ? 0 : timeDelta; - } - // produce a frame - mTestHandler.onAnimationFrame(getCurrentTime()); - } - - - /** - * Returns the current time in milliseconds tracked by AnimationHandler. Note that this is a - * different time than the time tracked by {@link SystemClock} This method needs to be called on - * the same thread as {@link Animator#start()}. - */ - public long getCurrentTime() { - if (Looper.myLooper() == null) { - // Throw an exception - throw new AndroidRuntimeException("AnimationTestRule#getCurrentTime() may only be" - + "called on Looper threads"); - } - synchronized (mLock) { - return mStartTime + mTotalTimeDelta; - } - } - - - private class TestProvider implements AnimationHandler.AnimationFrameCallbackProvider { - TestProvider() { - } - - @Override - public void onNewCallbackAdded(AnimationHandler.AnimationFrameCallback callback) { - callback.doAnimationFrame(getCurrentTime()); - } - - @Override - public void postFrameCallback() { - } - - @Override - public void setFrameDelay(long delay) { - } - - @Override - public long getFrameDelay() { - return 0; - } - } -} - diff --git a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt index bddd60b5970a..e7738aff6278 100644 --- a/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt +++ b/packages/SystemUI/tests/src/androidx/core/animation/AnimatorTestRuleTest.kt @@ -30,7 +30,7 @@ import org.junit.runner.RunWith @RunWithLooper(setAsMainLooper = true) class AnimatorTestRuleTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule2() + @get:Rule val animatorTestRule = AnimatorTestRule() @Test fun testA() { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 2f627cb5d9d7..b9f8dd945293 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -48,8 +48,10 @@ import com.android.systemui.keyguard.KeyguardUnlockAnimationController; import com.android.systemui.plugins.ClockAnimations; import com.android.systemui.plugins.ClockController; import com.android.systemui.plugins.ClockEvents; +import com.android.systemui.plugins.ClockFaceConfig; import com.android.systemui.plugins.ClockFaceController; import com.android.systemui.plugins.ClockFaceEvents; +import com.android.systemui.plugins.ClockTickRate; import com.android.systemui.plugins.log.LogBuffer; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.shared.clocks.AnimatableClockView; @@ -185,6 +187,10 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { when(mClockController.getAnimations()).thenReturn(mClockAnimations); when(mClockRegistry.createCurrentClock()).thenReturn(mClockController); when(mClockEventController.getClock()).thenReturn(mClockController); + when(mSmallClockController.getConfig()) + .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false)); + when(mLargeClockController.getConfig()) + .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, false)); mSliceView = new View(getContext()); when(mView.findViewById(R.id.keyguard_slice_view)).thenReturn(mSliceView); @@ -367,6 +373,28 @@ public class KeyguardClockSwitchControllerTest extends SysuiTestCase { } @Test + public void testChangeClockDateWeatherEnabled_SetsDateWeatherViewVisibility() { + ArgumentCaptor<ClockRegistry.ClockChangeListener> listenerArgumentCaptor = + ArgumentCaptor.forClass(ClockRegistry.ClockChangeListener.class); + when(mSmartspaceController.isEnabled()).thenReturn(true); + when(mSmartspaceController.isDateWeatherDecoupled()).thenReturn(true); + when(mSmartspaceController.isWeatherEnabled()).thenReturn(true); + mController.init(); + mExecutor.runAllReady(); + assertEquals(View.VISIBLE, mFakeDateView.getVisibility()); + + when(mSmallClockController.getConfig()) + .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true)); + when(mLargeClockController.getConfig()) + .thenReturn(new ClockFaceConfig(ClockTickRate.PER_MINUTE, true)); + verify(mClockRegistry).registerClockChangeListener(listenerArgumentCaptor.capture()); + listenerArgumentCaptor.getValue().onCurrentClockChanged(); + + mExecutor.runAllReady(); + assertEquals(View.GONE, mFakeDateView.getVisibility()); + } + + @Test public void testGetClock_nullClock_returnsNull() { when(mClockEventController.getClock()).thenReturn(null); assertNull(mController.getClock()); diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index d760189bcfd6..3e4fd891a668 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -67,6 +67,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; @@ -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 diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java index f1ee1080f6c0..2c1d2adb11ee 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java @@ -16,7 +16,9 @@ package com.android.keyguard; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -25,6 +27,9 @@ import com.android.internal.jank.InteractionJankMonitor; import com.android.keyguard.logging.KeyguardLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.flags.FeatureFlags; +import com.android.systemui.plugins.ClockConfig; +import com.android.systemui.plugins.ClockController; +import com.android.systemui.statusbar.notification.AnimatableProperty; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; import com.android.systemui.statusbar.policy.ConfigurationController; @@ -76,7 +81,16 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { mScreenOffAnimationController, mKeyguardLogger, mFeatureFlags, - mInteractionJankMonitor); + mInteractionJankMonitor) { + @Override + void setProperty( + AnimatableProperty property, + float value, + boolean animate) { + // Route into the mock version for verification + mControllerMock.setProperty(property, value, animate); + } + }; } @Test @@ -111,4 +125,34 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase { configurationListenerArgumentCaptor.getValue().onLocaleListChanged(); verify(mKeyguardClockSwitchController).onLocaleListChanged(); } + + @Test + public void updatePosition_primaryClockAnimation() { + ClockController mockClock = mock(ClockController.class); + when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock); + when(mockClock.getConfig()).thenReturn(new ClockConfig(false, false, true)); + + mController.updatePosition(10, 15, 20f, true); + + verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true); + verify(mKeyguardClockSwitchController).updatePosition( + 10, 20f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true); + verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 1f, true); + verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 1f, true); + } + + @Test + public void updatePosition_alternateClockAnimation() { + ClockController mockClock = mock(ClockController.class); + when(mKeyguardClockSwitchController.getClock()).thenReturn(mockClock); + when(mockClock.getConfig()).thenReturn(new ClockConfig(false, true, true)); + + mController.updatePosition(10, 15, 20f, true); + + verify(mControllerMock).setProperty(AnimatableProperty.Y, 15f, true); + verify(mKeyguardClockSwitchController).updatePosition( + 10, 1f, KeyguardStatusViewController.CLOCK_ANIMATION_PROPERTIES, true); + verify(mControllerMock).setProperty(AnimatableProperty.SCALE_X, 20f, true); + verify(mControllerMock).setProperty(AnimatableProperty.SCALE_Y, 20f, true); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java index 0978c824cb15..9c36af3a35e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java @@ -18,6 +18,7 @@ package com.android.systemui.accessibility; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; +import static android.content.res.Configuration.ORIENTATION_UNDEFINED; import static android.view.Choreographer.FrameCallback; import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_UP; @@ -172,6 +173,12 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { returnsSecondArg()); mResources = getContext().getOrCreateTestableResources().getResources(); + // prevent the config orientation from undefined, which may cause config.diff method + // neglecting the orientation update. + if (mResources.getConfiguration().orientation == ORIENTATION_UNDEFINED) { + mResources.getConfiguration().orientation = ORIENTATION_PORTRAIT; + } + mWindowMagnificationAnimationController = new WindowMagnificationAnimationController( mContext, mValueAnimator); mWindowMagnificationController = @@ -688,7 +695,11 @@ public class WindowMagnificationControllerTest extends SysuiTestCase { @Test public void enableWindowMagnification_rotationIsChanged_updateRotationValue() { - final Configuration config = mContext.getResources().getConfiguration(); + // the config orientation should not be undefined, since it would cause config.diff + // returning 0 and thus the orientation changed would not be detected + assertNotEquals(ORIENTATION_UNDEFINED, mResources.getConfiguration().orientation); + + final Configuration config = mResources.getConfiguration(); config.orientation = config.orientation == ORIENTATION_LANDSCAPE ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE; final int newRotation = simulateRotateTheDevice(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt index 921f9a8fc7d6..2b95973363ad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/back/OnBackAnimationCallbackExtensionTest.kt @@ -37,7 +37,13 @@ class OnBackAnimationCallbackExtensionTest : SysuiTestCase() { @Test fun onBackProgressed_shouldInvoke_onBackProgress() { - val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT) + val backEvent = + BackEvent( + /* touchX = */ 0f, + /* touchY = */ 0f, + /* progress = */ 0f, + /* swipeEdge = */ BackEvent.EDGE_LEFT + ) onBackAnimationCallback.onBackStarted(backEvent) onBackAnimationCallback.onBackProgressed(backEvent) @@ -47,7 +53,13 @@ class OnBackAnimationCallbackExtensionTest : SysuiTestCase() { @Test fun onBackStarted_shouldInvoke_onBackStart() { - val backEvent = BackEvent(0f, 0f, 0f, BackEvent.EDGE_LEFT) + val backEvent = + BackEvent( + /* touchX = */ 0f, + /* touchY = */ 0f, + /* progress = */ 0f, + /* swipeEdge = */ BackEvent.EDGE_LEFT + ) onBackAnimationCallback.onBackStarted(backEvent) 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/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 64c028e6a095..eae95a54744b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -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; @@ -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/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java index 08427dab978b..21397d97b578 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java @@ -269,6 +269,30 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { } @Test + public void testInputEventPropagationAfterRemoval() { + final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class); + + final Environment environment = new Environment(Stream.of(touchHandler) + .collect(Collectors.toCollection(HashSet::new))); + + final InputEvent initialEvent = Mockito.mock(InputEvent.class); + environment.publishInputEvent(initialEvent); + + // Ensure session started + final DreamTouchHandler.TouchSession session = captureSession(touchHandler); + final InputChannelCompat.InputEventListener eventListener = + registerInputEventListener(session); + + session.pop(); + environment.executeAll(); + + final InputEvent event = Mockito.mock(InputEvent.class); + environment.publishInputEvent(event); + + verify(eventListener, never()).onInputEvent(eq(event)); + } + + @Test public void testInputGesturePropagation() { final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class); 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 6e002f5a9a9a..f21ea3dbed6d 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 @@ -54,12 +54,15 @@ 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 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 @@ -81,6 +84,7 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock +import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.isNull import org.mockito.Mockito.mock @@ -120,6 +124,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { private lateinit var authStatus: FlowValue<AuthenticationStatus?> private lateinit var detectStatus: FlowValue<DetectionStatus?> private lateinit var authRunning: FlowValue<Boolean?> + private lateinit var bypassEnabled: FlowValue<Boolean?> private lateinit var lockedOut: FlowValue<Boolean?> private lateinit var canFaceAuthRun: FlowValue<Boolean?> private lateinit var authenticated: FlowValue<Boolean?> @@ -180,8 +185,11 @@ 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) + return DeviceEntryFaceAuthRepositoryImpl( mContext, fmOverride, fakeUserRepository, @@ -197,8 +205,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { keyguardRepository, keyguardInteractor, alternateBouncerInteractor, + faceDetectBuffer, + faceAuthBuffer, dumpManager, ) + } @Test fun faceAuthRunsAndProvidesAuthStatusUpdates() = @@ -726,6 +737,23 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { } @Test + fun isBypassEnabledReflectsBypassControllerState() = + testScope.runTest { + initCollectors() + runCurrent() + val listeners = captureMany { + verify(bypassController, atLeastOnce()) + .registerOnBypassStateChangedListener(capture()) + } + + listeners.forEach { it.onBypassStateChanged(true) } + assertThat(bypassEnabled()).isTrue() + + listeners.forEach { it.onBypassStateChanged(false) } + assertThat(bypassEnabled()).isFalse() + } + + @Test fun detectDoesNotRunWhenNonStrongBiometricIsAllowed() = testScope.runTest { testGatingCheckForDetect { @@ -844,6 +872,7 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() { lockedOut = collectLastValue(underTest.isLockedOut) canFaceAuthRun = collectLastValue(underTest.canRunFaceAuth) authenticated = collectLastValue(underTest.isAuthenticated) + bypassEnabled = collectLastValue(underTest.isBypassEnabled) fakeUserRepository.setSelectedUserInfo(primaryUser) } 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/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index aa92177e863e..e4d8b2598fe3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -46,7 +46,6 @@ import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; import com.android.internal.util.CollectionUtils; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; @@ -117,8 +116,6 @@ public class QSTileHostTest extends SysuiTestCase { @Mock private CustomTile mCustomTile; @Mock - private UiEventLogger mUiEventLogger; - @Mock private UserTracker mUserTracker; private SecureSettings mSecureSettings; @Mock @@ -164,7 +161,7 @@ public class QSTileHostTest extends SysuiTestCase { saveSetting(""); mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor, mPluginManager, mTunerService, mAutoTiles, mCentralSurfaces, - mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister, + mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister, mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags); mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) { @@ -684,14 +681,14 @@ public class QSTileHostTest extends SysuiTestCase { QSFactory defaultFactory, Executor mainExecutor, PluginManager pluginManager, TunerService tunerService, Provider<AutoTileManager> autoTiles, - CentralSurfaces centralSurfaces, QSLogger qsLogger, UiEventLogger uiEventLogger, + CentralSurfaces centralSurfaces, QSLogger qsLogger, UserTracker userTracker, SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister, TileLifecycleManager.Factory tileLifecycleManagerFactory, UserFileManager userFileManager, FeatureFlags featureFlags) { super(context, defaultFactory, mainExecutor, pluginManager, tunerService, autoTiles, Optional.of(centralSurfaces), qsLogger, - uiEventLogger, userTracker, secureSettings, customTileStatePersister, + userTracker, secureSettings, customTileStatePersister, tileLifecycleManagerFactory, userFileManager, featureFlags); } @@ -710,6 +707,7 @@ public class QSTileHostTest extends SysuiTestCase { protected TestTile(QSHost host) { super( host, + mock(QsEventLogger.class), mock(Looper.class), mock(Handler.class), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.kt new file mode 100644 index 000000000000..40aa2607dc94 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QsEventLoggerFake.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.qs + +import com.android.internal.logging.InstanceId +import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.testing.UiEventLoggerFake +import com.android.systemui.InstanceIdSequenceFake + +class QsEventLoggerFake( + uiEventLogger: UiEventLoggerFake, + private val instanceIdSequence: InstanceIdSequenceFake, +) : QsEventLogger, UiEventLogger by uiEventLogger { + + val lastInstanceId: Int + get() = instanceIdSequence.lastInstanceId + + override fun getNewInstanceId(): InstanceId { + return instanceIdSequence.newInstanceId() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index ac106ef9bf51..198ed4ac08fd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.settings.FakeDisplayTracker import com.android.systemui.util.mockito.any @@ -56,12 +57,12 @@ import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -89,6 +90,7 @@ class CustomTileTest : SysuiTestCase() { @Mock private lateinit var applicationInfo: ApplicationInfo @Mock private lateinit var serviceInfo: ServiceInfo @Mock private lateinit var customTileStatePersister: CustomTileStatePersister + @Mock private lateinit var uiEventLogger: QsEventLogger private var displayTracker = FakeDisplayTracker(mContext) private lateinit var customTile: CustomTile @@ -115,6 +117,7 @@ class CustomTileTest : SysuiTestCase() { customTileBuilder = CustomTile.Builder( { tileHost }, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index 36549fb826ec..962b53737274 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -61,6 +61,7 @@ import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import com.android.systemui.InstanceIdSequenceFake; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; @@ -69,6 +70,8 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSEvent; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; +import com.android.systemui.qs.QsEventLoggerFake; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.statusbar.StatusBarState; @@ -106,7 +109,8 @@ public class QSTileImplTest extends SysuiTestCase { private ActivityStarter mActivityStarter; private UiEventLoggerFake mUiEventLoggerFake; - private InstanceId mInstanceId = InstanceId.fakeInstanceId(5); + private QsEventLoggerFake mQsEventLoggerFake; + private InstanceId mInstanceId; @Captor private ArgumentCaptor<LogMaker> mLogCaptor; @@ -115,18 +119,29 @@ public class QSTileImplTest extends SysuiTestCase { public void setup() throws Exception { MockitoAnnotations.initMocks(this); mTestableLooper = TestableLooper.get(this); + mUiEventLoggerFake = new UiEventLoggerFake(); + mQsEventLoggerFake = + new QsEventLoggerFake(mUiEventLoggerFake, new InstanceIdSequenceFake(10)); when(mHost.indexOf(SPEC)).thenReturn(POSITION); when(mHost.getContext()).thenReturn(mContext); - when(mHost.getUiEventLogger()).thenReturn(mUiEventLoggerFake); - when(mHost.getNewInstanceId()).thenReturn(mInstanceId); Handler mainHandler = new Handler(mTestableLooper.getLooper()); - mTile = new TileImpl(mHost, mTestableLooper.getLooper(), mainHandler, mFalsingManager, - mMetricsLogger, mStatusBarStateController, mActivityStarter, mQsLogger); + mTile = new TileImpl( + mHost, + mQsEventLoggerFake, + mTestableLooper.getLooper(), + mainHandler, + mFalsingManager, + mMetricsLogger, + mStatusBarStateController, + mActivityStarter, + mQsLogger + ); mTile.initialize(); mTestableLooper.processAllMessages(); + mInstanceId = InstanceId.fakeInstanceId(mQsEventLoggerFake.getLastInstanceId()); mTile.setTileSpec(SPEC); } @@ -507,6 +522,7 @@ public class QSTileImplTest extends SysuiTestCase { protected TileImpl( QSHost host, + QsEventLogger uiEventLogger, Looper backgroundLooper, Handler mainHandler, FalsingManager falsingManager, @@ -515,7 +531,7 @@ public class QSTileImplTest extends SysuiTestCase { ActivityStarter activityStarter, QSLogger qsLogger ) { - super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, + super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); getState().state = Tile.STATE_ACTIVE; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt index 5e0190b65a12..c60cecb29d75 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt @@ -22,8 +22,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher @@ -32,6 +30,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.settings.UserTracker @@ -68,20 +67,21 @@ class AirplaneModeTileTest : SysuiTestCase() { private lateinit var mGlobalSettings: GlobalSettings @Mock private lateinit var mUserTracker: UserTracker + @Mock + private lateinit var mUiEventLogger: QsEventLogger private lateinit var mTestableLooper: TestableLooper private lateinit var mTile: AirplaneModeTile - private val mUiEventLogger: UiEventLogger = UiEventLoggerFake() - @Before fun setUp() { MockitoAnnotations.initMocks(this) mTestableLooper = TestableLooper.get(this) Mockito.`when`(mHost.context).thenReturn(mContext) - Mockito.`when`(mHost.uiEventLogger).thenReturn(mUiEventLogger) Mockito.`when`(mHost.userContext).thenReturn(mContext) - mTile = AirplaneModeTile(mHost, + mTile = AirplaneModeTile( + mHost, + mUiEventLogger, mTestableLooper.looper, Handler(mTestableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt index f1e3e8a71398..52b84559f396 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt @@ -9,12 +9,12 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.policy.NextAlarmController @@ -28,8 +28,8 @@ import org.junit.runner.RunWith import org.mockito.ArgumentCaptor import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest @@ -52,7 +52,7 @@ class AlarmTileTest : SysuiTestCase() { @Mock private lateinit var nextAlarmController: NextAlarmController @Mock - private lateinit var uiEventLogger: UiEventLogger + private lateinit var uiEventLogger: QsEventLogger @Mock private lateinit var pendingIntent: PendingIntent @Captor @@ -67,10 +67,10 @@ class AlarmTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) `when`(qsHost.context).thenReturn(mContext) - `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) tile = AlarmTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt index a5c0004afe02..ff6814c06001 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.BatteryController @@ -43,10 +44,10 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -63,6 +64,8 @@ class BatterySaverTileTest : SysuiTestCase() { @Mock private lateinit var qsHost: QSHost @Mock + private lateinit var uiEventLogger: QsEventLogger + @Mock private lateinit var metricsLogger: MetricsLogger @Mock private lateinit var statusBarStateController: StatusBarStateController @@ -90,6 +93,7 @@ class BatterySaverTileTest : SysuiTestCase() { tile = BatterySaverTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt index 2e77de270c65..5e7f68ccf3d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt @@ -9,7 +9,6 @@ import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.settingslib.Utils import com.android.settingslib.bluetooth.CachedBluetoothDevice import com.android.systemui.R @@ -20,6 +19,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.BluetoothController @@ -49,8 +49,8 @@ class BluetoothTileTest : SysuiTestCase() { @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var bluetoothController: BluetoothController + @Mock private lateinit var uiEventLogger: QsEventLogger - private val uiEventLogger = UiEventLoggerFake() private lateinit var testableLooper: TestableLooper private lateinit var tile: FakeBluetoothTile @@ -60,11 +60,11 @@ class BluetoothTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) whenever(qsHost.context).thenReturn(mContext) - whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger) tile = FakeBluetoothTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), falsingManager, @@ -211,6 +211,7 @@ class BluetoothTileTest : SysuiTestCase() { private class FakeBluetoothTile( qsHost: QSHost, + uiEventLogger: QsEventLogger, backgroundLooper: Looper, mainHandler: Handler, falsingManager: FalsingManager, @@ -222,6 +223,7 @@ class BluetoothTileTest : SysuiTestCase() { ) : BluetoothTile( qsHost, + uiEventLogger, backgroundLooper, mainHandler, falsingManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt index 41938541124a..70d82fdb6e5a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt @@ -21,8 +21,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -30,6 +28,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLoggerFake import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController @@ -67,19 +66,21 @@ class CameraToggleTileTest : SysuiTestCase() { private lateinit var privacyController: IndividualSensorPrivacyController @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var uiEventLogger: QsEventLoggerFake private lateinit var testableLooper: TestableLooper private lateinit var tile: CameraToggleTile - private val uiEventLogger: UiEventLogger = UiEventLoggerFake() @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) whenever(host.context).thenReturn(mContext) - whenever(host.uiEventLogger).thenReturn(uiEventLogger) - tile = CameraToggleTile(host, + tile = CameraToggleTile( + host, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), metricsLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java index 64fd09d5f5d9..93ed99423f0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -43,6 +43,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.statusbar.connectivity.IconState; import com.android.systemui.statusbar.connectivity.NetworkController; @@ -94,6 +95,8 @@ public class CastTileTest extends SysuiTestCase { private QSLogger mQSLogger; @Mock private DialogLaunchAnimator mDialogLaunchAnimator; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private CastTile mCastTile; @@ -107,6 +110,7 @@ public class CastTileTest extends SysuiTestCase { mCastTile = new CastTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java index 13c30e9ea9ab..2250ef33f9b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java @@ -32,12 +32,12 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.FakeSettings; @@ -67,7 +67,7 @@ public class ColorCorrectionTileTest extends SysuiTestCase { @Mock private QSLogger mQSLogger; @Mock - private UiEventLogger mUiEventLogger; + private QsEventLogger mUiEventLogger; @Mock private UserTracker mUserTracker; @@ -83,10 +83,10 @@ public class ColorCorrectionTileTest extends SysuiTestCase { mTestableLooper = TestableLooper.get(this); when(mHost.getContext()).thenReturn(mContext); - when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger); mTile = new ColorCorrectionTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java index ff27e0255aa3..2e02bbe2ebf2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java @@ -32,7 +32,6 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; @@ -40,6 +39,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; @@ -72,7 +72,7 @@ public class ColorInversionTileTest extends SysuiTestCase { @Mock private QSLogger mQSLogger; @Mock - private UiEventLogger mUiEventLogger; + private QsEventLogger mUiEventLogger; @Mock private UserTracker mUserTracker; @@ -88,10 +88,10 @@ public class ColorInversionTileTest extends SysuiTestCase { mTestableLooper = TestableLooper.get(this); when(mHost.getContext()).thenReturn(mContext); - when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger); mTile = new ColorInversionTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt index b048643aba84..176b33fa9fc8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt @@ -21,7 +21,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator @@ -30,6 +29,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.DataSaverController @@ -57,8 +57,8 @@ class DataSaverTileTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var dataSaverController: DataSaverController @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator + @Mock private lateinit var uiEventLogger: QsEventLogger - private val uiEventLogger = UiEventLoggerFake() private lateinit var testableLooper: TestableLooper private lateinit var tile: DataSaverTile @@ -68,11 +68,11 @@ class DataSaverTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) Mockito.`when`(mHost.context).thenReturn(mContext) - Mockito.`when`(mHost.uiEventLogger).thenReturn(uiEventLogger) tile = DataSaverTile( mHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), falsingManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt index b51c378f6b6b..1346069d3c25 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt @@ -27,7 +27,6 @@ import android.testing.TestableLooper import androidx.lifecycle.LifecycleOwner import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityLaunchAnimator @@ -44,6 +43,7 @@ import com.android.systemui.controls.ui.SelectedItem import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.util.mockito.any @@ -52,6 +52,7 @@ import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat +import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -59,15 +60,14 @@ import org.mockito.ArgumentCaptor import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Captor import org.mockito.Mock -import org.mockito.MockitoAnnotations -import org.mockito.Mockito.`when` import org.mockito.Mockito.doNothing import org.mockito.Mockito.nullable import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.verifyZeroInteractions +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations import java.util.Optional -import org.junit.After @SmallTest @RunWith(AndroidTestingRunner::class) @@ -95,7 +95,7 @@ class DeviceControlsTileTest : SysuiTestCase() { @Mock private lateinit var serviceInfo: ControlsServiceInfo @Mock - private lateinit var uiEventLogger: UiEventLogger + private lateinit var uiEventLogger: QsEventLogger @Captor private lateinit var listingCallbackCaptor: ArgumentCaptor<ControlsListingController.ControlsListingCallback> @@ -118,7 +118,6 @@ class DeviceControlsTileTest : SysuiTestCase() { spiedContext = spy(mContext) doNothing().`when`(spiedContext).startActivity(any(Intent::class.java)) `when`(qsHost.context).thenReturn(spiedContext) - `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) `when`(controlsComponent.isEnabled()).thenReturn(true) `when`(controlsController.getPreferredSelection()) .thenReturn(SelectedItem.StructureItem( @@ -399,6 +398,7 @@ class DeviceControlsTileTest : SysuiTestCase() { private fun createTile(): DeviceControlsTile { return DeviceControlsTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt index 6c0904eb9bfd..f0e4e3adda7c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt @@ -28,7 +28,6 @@ import android.testing.TestableLooper import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator @@ -37,6 +36,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.ZenModeController @@ -46,7 +46,6 @@ import com.android.systemui.util.mockito.nullable import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.google.common.truth.Truth.assertThat -import java.io.File import org.junit.After import org.junit.Before import org.junit.Test @@ -55,8 +54,9 @@ import org.mockito.Mock import org.mockito.Mockito.anyBoolean import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import java.io.File +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @@ -84,7 +84,7 @@ class DndTileTest : SysuiTestCase() { private lateinit var qsLogger: QSLogger @Mock - private lateinit var uiEventLogger: UiEventLogger + private lateinit var uiEventLogger: QsEventLogger @Mock private lateinit var zenModeController: ZenModeController @@ -109,7 +109,6 @@ class DndTileTest : SysuiTestCase() { secureSettings = FakeSettings() whenever(qsHost.userId).thenReturn(DEFAULT_USER) - whenever(qsHost.uiEventLogger).thenReturn(uiEventLogger) val wrappedContext = object : ContextWrapper(context) { override fun getSharedPreferences(file: File?, mode: Int): SharedPreferences { @@ -120,6 +119,7 @@ class DndTileTest : SysuiTestCase() { tile = DndTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java index 7d41aa6c3548..f231c6e56096 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java @@ -48,6 +48,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.settings.UserTracker; @@ -83,6 +84,8 @@ public class DreamTileTest extends SysuiTestCase { private BroadcastDispatcher mBroadcastDispatcher; @Mock private UserTracker mUserTracker; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; @@ -258,6 +261,7 @@ public class DreamTileTest extends SysuiTestCase { boolean dreamOnlyEnabledForSystemUser) { return new DreamTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt index 692a64422a7d..73aa6991c18a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt @@ -6,7 +6,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -14,6 +13,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.FlashlightController @@ -45,7 +45,8 @@ class FlashlightTileTest : SysuiTestCase() { @Mock private lateinit var flashlightController: FlashlightController - private val uiEventLogger = UiEventLoggerFake() + @Mock private lateinit var uiEventLogger: QsEventLogger + private val falsingManager = FalsingManagerFake() private lateinit var testableLooper: TestableLooper private lateinit var tile: FlashlightTile @@ -56,11 +57,11 @@ class FlashlightTileTest : SysuiTestCase() { testableLooper = TestableLooper.get(this) Mockito.`when`(qsHost.context).thenReturn(mockContext) - Mockito.`when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) tile = FlashlightTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), falsingManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt index eeebd4fb7792..1d6f225dd0a3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt @@ -23,7 +23,6 @@ import android.testing.TestableLooper import android.view.View import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogLaunchAnimator import com.android.systemui.classifier.FalsingManagerFake @@ -31,7 +30,8 @@ import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.qs.QSTileHost +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -52,13 +52,13 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class FontScalingTileTest : SysuiTestCase() { - @Mock private lateinit var qsHost: QSTileHost + @Mock private lateinit var qsHost: QSHost @Mock private lateinit var metricsLogger: MetricsLogger @Mock private lateinit var statusBarStateController: StatusBarStateController @Mock private lateinit var activityStarter: ActivityStarter @Mock private lateinit var qsLogger: QSLogger @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator - @Mock private lateinit var uiEventLogger: UiEventLogger + @Mock private lateinit var uiEventLogger: QsEventLogger private lateinit var testableLooper: TestableLooper private lateinit var fontScalingTile: FontScalingTile @@ -70,11 +70,11 @@ class FontScalingTileTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) `when`(qsHost.getContext()).thenReturn(mContext) - `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) fontScalingTile = FontScalingTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java index 959e750ac5f4..73f61d0690e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java @@ -38,6 +38,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.DataSaverController; @@ -66,6 +67,8 @@ public class HotspotTileTest extends SysuiTestCase { private HotspotController mHotspotController; @Mock private DataSaverController mDataSaverController; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private HotspotTile mTile; @@ -80,6 +83,7 @@ public class HotspotTileTest extends SysuiTestCase { mTile = new HotspotTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java index adfd7f71e8f8..7957c6a7cfb6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java @@ -35,6 +35,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.qs.tiles.dialog.InternetDialogFactory; @@ -63,6 +64,8 @@ public class InternetTileTest extends SysuiTestCase { private AccessPointController mAccessPointController; @Mock private InternetDialogFactory mInternetDialogFactory; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private InternetTile mTile; @@ -76,6 +79,7 @@ public class InternetTileTest extends SysuiTestCase { mTile = new InternetTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt index 3642e874e7ae..0bf0b38f7471 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt @@ -22,7 +22,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -30,6 +29,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor import com.android.systemui.qs.tileimpl.QSTileImpl @@ -43,8 +43,8 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -71,8 +71,9 @@ class LocationTileTest : SysuiTestCase() { private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var panelInteractor: PanelInteractor + @Mock + private lateinit var uiEventLogger: QsEventLogger - private val uiEventLogger = UiEventLoggerFake() private lateinit var testableLooper: TestableLooper private lateinit var tile: LocationTile @@ -80,10 +81,11 @@ class LocationTileTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) - `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) `when`(qsHost.context).thenReturn(mockContext) - tile = LocationTile(qsHost, + tile = LocationTile( + qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), falsingManager, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt index e2f64b2cc226..ceff546106f4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt @@ -21,8 +21,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -30,6 +28,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController @@ -67,19 +66,22 @@ class MicrophoneToggleTileTest : SysuiTestCase() { private lateinit var privacyController: IndividualSensorPrivacyController @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var uiEventLogger: QsEventLogger private lateinit var testableLooper: TestableLooper private lateinit var tile: MicrophoneToggleTile - private val uiEventLogger: UiEventLogger = UiEventLoggerFake() + @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) whenever(host.context).thenReturn(mContext) - whenever(host.uiEventLogger).thenReturn(uiEventLogger) - tile = MicrophoneToggleTile(host, + tile = MicrophoneToggleTile( + host, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), metricsLogger, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java index c7dae83e2056..763a7e5e4e06 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java @@ -37,6 +37,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import org.junit.After; @@ -70,6 +71,8 @@ public class NfcTileTest extends SysuiTestCase { private QSLogger mQSLogger; @Mock private BroadcastDispatcher mBroadcastDispatcher; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private NfcTile mNfcTile; @@ -84,6 +87,7 @@ public class NfcTileTest extends SysuiTestCase { mNfcTile = new NfcTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt index 04af69c84cf8..6c8f76b48f03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt @@ -23,8 +23,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.UiEventLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -33,6 +31,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.LocationController @@ -43,8 +42,8 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.anyInt -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @@ -68,17 +67,18 @@ class NightDisplayTileTest : SysuiTestCase() { @Mock private lateinit var mNightDisplayListener: NightDisplayListener + @Mock private lateinit var mUiEventLogger: QsEventLogger + private lateinit var mTestableLooper: TestableLooper private lateinit var mTile: NightDisplayTile - private val mUiEventLogger: UiEventLogger = UiEventLoggerFake() + @Before fun setUp() { MockitoAnnotations.initMocks(this) mTestableLooper = TestableLooper.get(this) whenever(mHost.context).thenReturn(mContext) - whenever(mHost.uiEventLogger).thenReturn(mUiEventLogger) whenever(mHost.userContext).thenReturn(mContext) whenever(mNightDisplayListenerBuilder.setUser(anyInt())) .thenReturn(mNightDisplayListenerBuilder) @@ -87,6 +87,7 @@ class NightDisplayTileTest : SysuiTestCase() { mTile = NightDisplayTile( mHost, + mUiEventLogger, mTestableLooper.looper, Handler(mTestableLooper.looper), FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java index 652c138f6478..c391153fecc1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java @@ -33,6 +33,7 @@ import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.settings.UserTracker; import com.android.systemui.util.settings.SecureSettings; @@ -65,6 +66,8 @@ public class OneHandedModeTileTest extends SysuiTestCase { private UserTracker mUserTracker; @Mock private SecureSettings mSecureSettings; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private OneHandedModeTile mTile; @@ -78,6 +81,7 @@ public class OneHandedModeTileTest extends SysuiTestCase { mTile = spy(new OneHandedModeTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java index 3125d455acfb..6f2d904dda64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java @@ -30,8 +30,6 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; @@ -40,6 +38,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qrcodescanner.controller.QRCodeScannerController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -64,7 +63,8 @@ public class QRCodeScannerTileTest extends SysuiTestCase { private ActivityStarter mActivityStarter; @Mock private QSLogger mQSLogger; - private UiEventLogger mUiEventLogger = new UiEventLoggerFake(); + @Mock + private QsEventLogger mUiEventLogger; @Mock private QRCodeScannerController mController; @@ -79,6 +79,7 @@ public class QRCodeScannerTileTest extends SysuiTestCase { mTile = new QRCodeScannerTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index 596df7856ee1..b089e380304d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -58,8 +58,6 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.UiEventLogger; -import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; @@ -67,6 +65,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -109,7 +108,8 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private ActivityStarter mActivityStarter; @Mock private QSLogger mQSLogger; - private UiEventLogger mUiEventLogger = new UiEventLoggerFake(); + @Mock + private QsEventLogger mUiEventLogger; @Mock private QuickAccessWalletClient mQuickAccessWalletClient; @Mock @@ -136,7 +136,6 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { doNothing().when(mSpiedContext).startActivity(any(Intent.class)); when(mHost.getContext()).thenReturn(mSpiedContext); - when(mHost.getUiEventLogger()).thenReturn(mUiEventLogger); when(mQuickAccessWalletClient.getServiceLabel()).thenReturn(LABEL); when(mQuickAccessWalletClient.getTileIcon()).thenReturn(mTileIcon); when(mQuickAccessWalletClient.isWalletFeatureAvailable()).thenReturn(true); @@ -146,6 +145,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { mTile = new QuickAccessWalletTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java index 7913628c5693..d2445944c182 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java @@ -39,6 +39,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -69,6 +70,8 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { private UserTracker mUserTracker; @Mock private ReduceBrightColorsController mReduceBrightColorsController; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private ReduceBrightColorsTile mTile; @@ -85,6 +88,7 @@ public class ReduceBrightColorsTileTest extends SysuiTestCase { true, mReduceBrightColorsController, mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java index 5b94cfedaedf..e106741499ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java @@ -39,6 +39,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.BatteryController; @@ -87,6 +88,8 @@ public class RotationLockTileTest extends SysuiTestCase { DeviceStateRotationLockSettingController mDeviceStateRotationLockSettingController; @Mock RotationPolicyWrapper mRotationPolicyWrapper; + @Mock + QsEventLogger mUiEventLogger; private RotationLockController mController; private TestableLooper mTestableLooper; @@ -105,6 +108,7 @@ public class RotationLockTileTest extends SysuiTestCase { mLockTile = new RotationLockTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index d9ed1a299f51..fff2b8f5f8cd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -44,6 +44,7 @@ import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.QsEventLogger; import com.android.systemui.qs.logging.QSLogger; import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -86,6 +87,8 @@ public class ScreenRecordTileTest extends SysuiTestCase { private DialogLaunchAnimator mDialogLaunchAnimator; @Mock private PanelInteractor mPanelInteractor; + @Mock + private QsEventLogger mUiEventLogger; private TestableLooper mTestableLooper; private ScreenRecordTile mTile; @@ -100,6 +103,7 @@ public class ScreenRecordTileTest extends SysuiTestCase { mTile = new ScreenRecordTile( mHost, + mUiEventLogger, mTestableLooper.getLooper(), new Handler(mTestableLooper.getLooper()), new FalsingManagerFake(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt index b55657163382..79147e7e66b4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt @@ -25,7 +25,6 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger -import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingManagerFake @@ -33,6 +32,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.BatteryController @@ -63,8 +63,8 @@ class UiModeNightTileTest : SysuiTestCase() { @Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var batteryController: BatteryController @Mock private lateinit var locationController: LocationController + @Mock private lateinit var uiEventLogger: QsEventLogger - private val uiEventLogger = UiEventLoggerFake() private val falsingManager = FalsingManagerFake() private lateinit var testableLooper: TestableLooper private lateinit var tile: UiModeNightTile @@ -81,11 +81,11 @@ class UiModeNightTileTest : SysuiTestCase() { `when`(qsHost.userContext).thenReturn(mContext) `when`(mockContext.resources).thenReturn(resources) `when`(resources.configuration).thenReturn(configuration) - `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger) tile = UiModeNightTile( qsHost, + uiEventLogger, testableLooper.looper, Handler(testableLooper.looper), falsingManager, 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..4bfd6a2e28f4 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; @@ -665,7 +666,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mMetricsLogger, mFeatureFlags, mInteractionJankMonitor, - mShadeLog + mShadeLog, + mock(KeyguardFaceAuthInteractor.class) ); } 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/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java index 569f90b64609..4438b98f6fad 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java @@ -614,9 +614,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void onBiometricHelp_coEx_faceFailure() { createController(); - // GIVEN unlocking with fingerprint is possible - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt())) - .thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); String message = "A message"; mController.setVisible(true); @@ -641,9 +640,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void onBiometricHelp_coEx_faceUnavailable() { createController(); - // GIVEN unlocking with fingerprint is possible - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt())) - .thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); String message = "A message"; mController.setVisible(true); @@ -664,6 +662,35 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { mContext.getString(R.string.keyguard_suggest_fingerprint)); } + + @Test + public void onBiometricHelp_coEx_faceUnavailable_fpNotAllowed() { + createController(); + + // GIVEN unlocking with fingerprint is possible but not allowed + setupFingerprintUnlockPossible(true); + when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()) + .thenReturn(false); + + String message = "A message"; + mController.setVisible(true); + + // WHEN there's a face unavailable message + mController.getKeyguardCallback().onBiometricHelp( + BIOMETRIC_HELP_FACE_NOT_AVAILABLE, + message, + BiometricSourceType.FACE); + + // THEN show sequential messages such as: 'face unlock unavailable' and + // 'try fingerprint instead' + verifyIndicationMessage( + INDICATION_TYPE_BIOMETRIC_MESSAGE, + message); + verifyIndicationMessage( + INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, + mContext.getString(R.string.keyguard_unlock)); + } + @Test public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() { createController(); @@ -818,8 +845,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void faceErrorTimeout_whenFingerprintEnrolled_doesNotShowMessage() { createController(); - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(true); + fingerprintUnlockIsPossibleAndAllowed(); String message = "A message"; mController.setVisible(true); @@ -832,9 +858,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void sendFaceHelpMessages_fingerprintEnrolled() { createController(); - // GIVEN fingerprint enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); // WHEN help messages received that are allowed to show final String helpString = "helpString"; @@ -859,9 +884,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void doNotSendMostFaceHelpMessages_fingerprintEnrolled() { createController(); - // GIVEN fingerprint enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); // WHEN help messages received that aren't supposed to show final String helpString = "helpString"; @@ -886,9 +910,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void sendAllFaceHelpMessages_fingerprintNotEnrolled() { createController(); - // GIVEN fingerprint NOT enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(false); + // GIVEN fingerprint NOT possible + fingerprintUnlockIsNotPossible(); // WHEN help messages received final Set<CharSequence> helpStrings = new HashSet<>(); @@ -917,9 +940,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void sendTooDarkFaceHelpMessages_onTimeout_noFpEnrolled() { createController(); - // GIVEN fingerprint NOT enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(false); + // GIVEN fingerprint not possible + fingerprintUnlockIsNotPossible(); // WHEN help message received and deferred message is valid final String helpString = "helpMsg"; @@ -948,9 +970,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void sendTooDarkFaceHelpMessages_onTimeout_fingerprintEnrolled() { createController(); - // GIVEN fingerprint enrolled - when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible( - getCurrentUser())).thenReturn(true); + // GIVEN unlocking with fingerprint is possible and allowed + fingerprintUnlockIsPossibleAndAllowed(); // WHEN help message received and deferredMessage is valid final String helpString = "helpMsg"; @@ -1500,7 +1521,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void onBiometricError_faceLockedOutFirstTimeAndFpAllowed_showsTheFpFollowupMessage() { createController(); - fingerprintUnlockIsPossible(); + fingerprintUnlockIsPossibleAndAllowed(); onFaceLockoutError("first lockout"); verifyIndicationShown(INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP, @@ -1559,7 +1580,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { @Test public void onBiometricError_faceLockedOutAgainAndFpAllowed_showsTheFpFollowupMessage() { createController(); - fingerprintUnlockIsPossible(); + fingerprintUnlockIsPossibleAndAllowed(); onFaceLockoutError("first lockout"); clearInvocations(mRotateTextViewController); @@ -1668,7 +1689,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { public void onBiometricError_screenIsTurningOn_faceLockedOutFpIsAvailable_showsMessage() { createController(); screenIsTurningOn(); - fingerprintUnlockIsPossible(); + fingerprintUnlockIsPossibleAndAllowed(); onFaceLockoutError("lockout error"); verifyNoMoreInteractions(mRotateTextViewController); @@ -1746,8 +1767,9 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase { setupFingerprintUnlockPossible(false); } - private void fingerprintUnlockIsPossible() { + private void fingerprintUnlockIsPossibleAndAllowed() { setupFingerprintUnlockPossible(true); + when(mKeyguardUpdateMonitor.isUnlockingWithFingerprintAllowed()).thenReturn(true); } private void setupFingerprintUnlockPossible(boolean possible) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt index 7b59cc284181..08a9f3139d71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt @@ -22,7 +22,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import android.view.View import android.widget.FrameLayout -import androidx.core.animation.AnimatorTestRule2 +import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -70,7 +70,7 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() { private lateinit var systemStatusAnimationScheduler: SystemStatusAnimationScheduler private val fakeFeatureFlags = FakeFeatureFlags() - @get:Rule val animatorTestRule = AnimatorTestRule2() + @get:Rule val animatorTestRule = AnimatorTestRule() @Before fun setup() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt index be3b7234a1a2..aff705f1f3bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinatorTest.kt @@ -18,7 +18,7 @@ package com.android.systemui.statusbar.notification import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import androidx.core.animation.AnimatorTestRule2 +import androidx.core.animation.AnimatorTestRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -51,7 +51,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions @TestableLooper.RunWithLooper(setAsMainLooper = true) class NotificationWakeUpCoordinatorTest : SysuiTestCase() { - @get:Rule val animatorTestRule = AnimatorTestRule2() + @get:Rule val animatorTestRule = AnimatorTestRule() private val dumpManager: DumpManager = mock() private val headsUpManager: HeadsUpManager = mock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index 7d022192f935..9186c35e2b47 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -100,7 +100,6 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { mNotificationTestHelper.setDefaultInflationFlags(FLAG_CONTENT_VIEW_ALL); FakeFeatureFlags fakeFeatureFlags = new FakeFeatureFlags(); - fakeFeatureFlags.set(Flags.NOTIFICATION_ANIMATE_BIG_PICTURE, true); fakeFeatureFlags.set(Flags.SENSITIVE_REVEAL_ANIM, false); mNotificationTestHelper.setFeatureFlags(fakeFeatureFlags); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt new file mode 100644 index 000000000000..2cc375b3d0ed --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/domain/interactor/NotificationShelfInteractorTest.kt @@ -0,0 +1,88 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification.shelf.domain.interactor + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class NotificationShelfInteractorTest : SysuiTestCase() { + + private val keyguardRepository = FakeKeyguardRepository() + private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository() + private val underTest = + NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository) + + @Test + fun shelfIsNotStatic_whenKeyguardNotShowing() = runTest { + val shelfStatic by collectLastValue(underTest.isShelfStatic) + + keyguardRepository.setKeyguardShowing(false) + + assertThat(shelfStatic).isFalse() + } + + @Test + fun shelfIsNotStatic_whenKeyguardShowingAndNotBypass() = runTest { + val shelfStatic by collectLastValue(underTest.isShelfStatic) + + keyguardRepository.setKeyguardShowing(true) + deviceEntryFaceAuthRepository.isBypassEnabled.value = false + + assertThat(shelfStatic).isFalse() + } + + @Test + fun shelfIsStatic_whenBypass() = runTest { + val shelfStatic by collectLastValue(underTest.isShelfStatic) + + keyguardRepository.setKeyguardShowing(true) + deviceEntryFaceAuthRepository.isBypassEnabled.value = true + + assertThat(shelfStatic).isTrue() + } + + @Test + fun shelfOnKeyguard_whenKeyguardShowing() = runTest { + val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard) + + keyguardRepository.setKeyguardShowing(true) + + assertThat(onKeyguard).isTrue() + } + + @Test + fun shelfNotOnKeyguard_whenKeyguardNotShowing() = runTest { + val onKeyguard by collectLastValue(underTest.isShowingOnKeyguard) + + keyguardRepository.setKeyguardShowing(false) + + assertThat(onKeyguard).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt new file mode 100644 index 000000000000..439edaf3faaf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/shelf/ui/viewmodel/NotificationShelfViewModelTest.kt @@ -0,0 +1,90 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.statusbar.notification.shelf.ui.viewmodel + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository +import com.android.systemui.statusbar.notification.shelf.domain.interactor.NotificationShelfInteractor +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class NotificationShelfViewModelTest : SysuiTestCase() { + + private val keyguardRepository = FakeKeyguardRepository() + private val deviceEntryFaceAuthRepository = FakeDeviceEntryFaceAuthRepository() + private val interactor = + NotificationShelfInteractor(keyguardRepository, deviceEntryFaceAuthRepository) + private val underTest = NotificationShelfViewModel(interactor) + + @Test + fun canModifyColorOfNotifications_whenKeyguardNotShowing() = runTest { + val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications) + + keyguardRepository.setKeyguardShowing(false) + + assertThat(canModifyNotifColor).isTrue() + } + + @Test + fun canModifyColorOfNotifications_whenKeyguardShowingAndNotBypass() = runTest { + val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications) + + keyguardRepository.setKeyguardShowing(true) + deviceEntryFaceAuthRepository.isBypassEnabled.value = false + + assertThat(canModifyNotifColor).isTrue() + } + + @Test + fun cannotModifyColorOfNotifications_whenBypass() = runTest { + val canModifyNotifColor by collectLastValue(underTest.canModifyColorOfNotifications) + + keyguardRepository.setKeyguardShowing(true) + deviceEntryFaceAuthRepository.isBypassEnabled.value = true + + assertThat(canModifyNotifColor).isFalse() + } + + @Test + fun isClickable_whenKeyguardShowing() = runTest { + val isClickable by collectLastValue(underTest.isClickable) + + keyguardRepository.setKeyguardShowing(true) + + assertThat(isClickable).isTrue() + } + + @Test + fun isNotClickable_whenKeyguardNotShowing() = runTest { + val isClickable by collectLastValue(underTest.isClickable) + + keyguardRepository.setKeyguardShowing(false) + + assertThat(isClickable).isFalse() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt index e6f10cdafe03..5279740a8702 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt @@ -23,6 +23,7 @@ import android.view.View.VISIBLE import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase +import com.android.systemui.media.controls.pipeline.MediaDataManager import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -48,6 +49,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController + @Mock private lateinit var mediaDataManager: MediaDataManager @Mock private lateinit var stackLayout: NotificationStackScrollLayout private val testableResources = mContext.orCreateTestableResources @@ -67,7 +69,9 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { NotificationStackSizeCalculator( statusBarStateController = sysuiStatusBarStateController, lockscreenShadeTransitionController = lockscreenShadeTransitionController, - testableResources.resources) + mediaDataManager = mediaDataManager, + testableResources.resources + ) } @Test @@ -76,7 +80,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { val maxNotifications = computeMaxKeyguardNotifications( - rows, spaceForNotifications = 0f, spaceForShelf = 0f, shelfHeight = 0f) + rows, + spaceForNotifications = 0f, + spaceForShelf = 0f, + shelfHeight = 0f + ) assertThat(maxNotifications).isEqualTo(0) } @@ -91,7 +99,8 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { rows, spaceForNotifications = Float.MAX_VALUE, spaceForShelf = Float.MAX_VALUE, - shelfHeight) + shelfHeight + ) assertThat(maxNotifications).isEqualTo(numberOfRows) } @@ -111,6 +120,28 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { } @Test + fun computeMaxKeyguardNotifications_onLockscreenSpaceForMinHeightButNotIntrinsicHeight_returnsOne() { + setGapHeight(0f) + // No divider height since we're testing one element where index = 0 + + whenever(sysuiStatusBarStateController.state).thenReturn(StatusBarState.KEYGUARD) + whenever(lockscreenShadeTransitionController.fractionToShade).thenReturn(0f) + + val row = createMockRow(10f, isSticky = true) + whenever(row.getMinHeight(any())).thenReturn(5) + + val maxNotifications = + computeMaxKeyguardNotifications( + listOf(row), + /* spaceForNotifications= */ 5f, + /* spaceForShelf= */ 0f, + /* shelfHeight= */ 0f + ) + + assertThat(maxNotifications).isEqualTo(1) + } + + @Test fun computeMaxKeyguardNotifications_spaceForTwo_returnsTwo() { setGapHeight(gapHeight) val shelfHeight = shelfHeight + dividerHeight @@ -126,7 +157,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { val maxNotifications = computeMaxKeyguardNotifications( - rows, spaceForNotifications + 1, spaceForShelf, shelfHeight) + rows, + spaceForNotifications + 1, + spaceForShelf, + shelfHeight + ) assertThat(maxNotifications).isEqualTo(2) } @@ -136,24 +171,23 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { // Each row in separate section. setGapHeight(gapHeight) - val spaceForNotifications = + val notifSpace = listOf( rowHeight, dividerHeight + gapHeight + rowHeight, ) .sum() - val spaceForShelf = dividerHeight + gapHeight + shelfHeight - val spaceUsed = spaceForNotifications + spaceForShelf + val shelfSpace = dividerHeight + gapHeight + shelfHeight + val spaceUsed = notifSpace + shelfSpace val rows = listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight)) val maxNotifications = - computeMaxKeyguardNotifications(rows, spaceForNotifications, spaceForShelf, shelfHeight) + computeMaxKeyguardNotifications(rows, notifSpace, shelfSpace, shelfHeight) assertThat(maxNotifications).isEqualTo(2) - val height = - sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) + val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) assertThat(height).isEqualTo(spaceUsed) } @@ -170,11 +204,14 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { // test that we only use space required val maxNotifications = computeMaxKeyguardNotifications( - rows, spaceForNotifications + 1, spaceForShelf, shelfHeight) + rows, + spaceForNotifications + 1, + spaceForShelf, + shelfHeight + ) assertThat(maxNotifications).isEqualTo(1) - val height = - sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) + val height = sizeCalculator.computeHeight(stackLayout, maxNotifications, this.shelfHeight) assertThat(height).isEqualTo(spaceUsed) } @@ -200,60 +237,101 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { } @Test - fun spaceNeeded_onLockscreen_usesMinHeight() { + fun getSpaceNeeded_onLockscreenEnoughSpaceStickyHun_intrinsicHeight() { setGapHeight(0f) // No divider height since we're testing one element where index = 0 - val expandableView = createMockRow(rowHeight) + val row = createMockRow(10f, isSticky = true) + whenever(row.getMinHeight(any())).thenReturn(5) + + val space = + sizeCalculator.getSpaceNeeded( + row, + visibleIndex = 0, + previousView = null, + stack = stackLayout, + onLockscreen = true + ) + assertThat(space.whenEnoughSpace).isEqualTo(10f) + } + + @Test + fun getSpaceNeeded_onLockscreenEnoughSpaceNotStickyHun_minHeight() { + setGapHeight(0f) + // No divider height since we're testing one element where index = 0 + + val row = createMockRow(rowHeight) + whenever(row.heightWithoutLockscreenConstraints).thenReturn(10) + whenever(row.getMinHeight(any())).thenReturn(5) + + val space = + sizeCalculator.getSpaceNeeded( + row, + visibleIndex = 0, + previousView = null, + stack = stackLayout, + onLockscreen = true + ) + assertThat(space.whenEnoughSpace).isEqualTo(5) + } + + @Test + fun getSpaceNeeded_onLockscreenSavingSpaceStickyHun_minHeight() { + setGapHeight(0f) + // No divider height since we're testing one element where index = 0 + + val expandableView = createMockRow(10f, isSticky = true) whenever(expandableView.getMinHeight(any())).thenReturn(5) - whenever(expandableView.intrinsicHeight).thenReturn(10) val space = - sizeCalculator.spaceNeeded( + sizeCalculator.getSpaceNeeded( expandableView, visibleIndex = 0, previousView = null, stack = stackLayout, - onLockscreen = true) - assertThat(space).isEqualTo(5) + onLockscreen = true + ) + assertThat(space.whenSavingSpace).isEqualTo(5) } @Test - fun spaceNeeded_fsiHunOnLockscreen_usesIntrinsicHeight() { + fun getSpaceNeeded_onLockscreenSavingSpaceNotStickyHun_minHeight() { setGapHeight(0f) // No divider height since we're testing one element where index = 0 - val expandableView = createMockStickyRow(rowHeight) + val expandableView = createMockRow(rowHeight) whenever(expandableView.getMinHeight(any())).thenReturn(5) whenever(expandableView.intrinsicHeight).thenReturn(10) val space = - sizeCalculator.spaceNeeded( - expandableView, - visibleIndex = 0, - previousView = null, - stack = stackLayout, - onLockscreen = true) - assertThat(space).isEqualTo(10) + sizeCalculator.getSpaceNeeded( + expandableView, + visibleIndex = 0, + previousView = null, + stack = stackLayout, + onLockscreen = true + ) + assertThat(space.whenSavingSpace).isEqualTo(5) } @Test - fun spaceNeeded_notOnLockscreen_usesIntrinsicHeight() { + fun getSpaceNeeded_notOnLockscreen_intrinsicHeight() { setGapHeight(0f) // No divider height since we're testing one element where index = 0 val expandableView = createMockRow(rowHeight) - whenever(expandableView.getMinHeight(any())).thenReturn(5) - whenever(expandableView.intrinsicHeight).thenReturn(10) + whenever(expandableView.getMinHeight(any())).thenReturn(1) val space = - sizeCalculator.spaceNeeded( + sizeCalculator.getSpaceNeeded( expandableView, visibleIndex = 0, previousView = null, stack = stackLayout, - onLockscreen = false) - assertThat(space).isEqualTo(10) + onLockscreen = false + ) + assertThat(space.whenEnoughSpace).isEqualTo(rowHeight) + assertThat(space.whenSavingSpace).isEqualTo(rowHeight) } private fun computeMaxKeyguardNotifications( @@ -264,7 +342,11 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { ): Int { setupChildren(rows) return sizeCalculator.computeMaxKeyguardNotifications( - stackLayout, spaceForNotifications, spaceForShelf, shelfHeight) + stackLayout, + spaceForNotifications, + spaceForShelf, + shelfHeight + ) } private fun setupChildren(children: List<ExpandableView>) { @@ -280,30 +362,13 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { private fun createMockRow( height: Float = rowHeight, + isSticky: Boolean = false, isRemoved: Boolean = false, - visibility: Int = VISIBLE - ): ExpandableNotificationRow { - val row = mock(ExpandableNotificationRow::class.java) - val entry = mock(NotificationEntry::class.java) - val sbn = mock(StatusBarNotification::class.java) - whenever(entry.sbn).thenReturn(sbn) - whenever(row.entry).thenReturn(entry) - whenever(row.isRemoved).thenReturn(isRemoved) - whenever(row.visibility).thenReturn(visibility) - whenever(row.getMinHeight(any())).thenReturn(height.toInt()) - whenever(row.intrinsicHeight).thenReturn(height.toInt()) - return row - } - - private fun createMockStickyRow( - height: Float = rowHeight, - isRemoved: Boolean = false, - visibility: Int = VISIBLE + visibility: Int = VISIBLE, ): ExpandableNotificationRow { val row = mock(ExpandableNotificationRow::class.java) val entry = mock(NotificationEntry::class.java) - whenever(entry.isStickyAndNotDemoted).thenReturn(true) - + whenever(entry.isStickyAndNotDemoted).thenReturn(isSticky) val sbn = mock(StatusBarNotification::class.java) whenever(entry.sbn).thenReturn(sbn) whenever(row.entry).thenReturn(entry) @@ -311,6 +376,7 @@ class NotificationStackSizeCalculatorTest : SysuiTestCase() { whenever(row.visibility).thenReturn(visibility) whenever(row.getMinHeight(any())).thenReturn(height.toInt()) whenever(row.intrinsicHeight).thenReturn(height.toInt()) + whenever(row.heightWithoutLockscreenConstraints).thenReturn(height.toInt()) return row } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 14aee4e13a8f..baeb188cd4a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -20,6 +20,7 @@ import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerCons import static com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -73,6 +74,7 @@ import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.TaskbarDelegate; import com.android.systemui.plugins.ActivityStarter.OnDismissAction; +import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationPanelViewController; import com.android.systemui.shade.NotificationShadeWindowView; import com.android.systemui.shade.ShadeController; @@ -192,7 +194,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mPrimaryBouncerInteractor, mBouncerView, mAlternateBouncerInteractor, - mUdfpsOverlayInteractor) { + mUdfpsOverlayInteractor, + mock(UserTracker.class)) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -216,14 +219,34 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - public void dismissWithAction_AfterKeyguardGoneSetToFalse() { + public void dismissWithAction_AfterKeyguardGoneSetToFalse_DismissAction_Set() { OnDismissAction action = () -> false; Runnable cancelAction = () -> { }; + when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true); + mStatusBarKeyguardViewManager.dismissWithAction( action, cancelAction, false /* afterKeyguardGone */); + verify(mPrimaryBouncerInteractor).setDismissAction(eq(action), eq(cancelAction)); verify(mPrimaryBouncerInteractor).show(eq(true)); + assertNull(mStatusBarKeyguardViewManager.mAfterKeyguardGoneAction); + assertNull(mStatusBarKeyguardViewManager.mKeyguardGoneCancelAction); + } + + @Test + public void dismissWithAction_AfterKeyguardGoneSetToFalse_DismissAction_Called() { + OnDismissAction action = mock(OnDismissAction.class); + Runnable cancelAction = () -> { + }; + when(mLockPatternUtils.isSecure(anyInt())).thenReturn(false); + + mStatusBarKeyguardViewManager.dismissWithAction( + action, cancelAction, false /* afterKeyguardGone */); + + verify(action).onDismiss(); + assertNull(mStatusBarKeyguardViewManager.mAfterKeyguardGoneAction); + assertNull(mStatusBarKeyguardViewManager.mKeyguardGoneCancelAction); } @Test @@ -680,7 +703,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mPrimaryBouncerInteractor, mBouncerView, mAlternateBouncerInteractor, - mUdfpsOverlayInteractor) { + mUdfpsOverlayInteractor, + mock(UserTracker.class)) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; 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/ui/viewmodel/MobileIconsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModelTest.kt index ddb7f4d88d30..01bec879102d 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 @@ -32,9 +32,8 @@ import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnec import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest @@ -69,14 +68,8 @@ class MobileIconsViewModelTest : SysuiTestCase() { FakeConnectivityRepository(), ) - val subscriptionIdsFlow = - interactor.filteredSubscriptions - .map { subs -> subs.map { it.subscriptionId } } - .stateIn(testScope.backgroundScope, SharingStarted.WhileSubscribed(), listOf()) - underTest = MobileIconsViewModel( - subscriptionIdsFlow, logger, verboseLogger, interactor, @@ -90,6 +83,32 @@ class MobileIconsViewModelTest : SysuiTestCase() { } @Test + fun subscriptionIdsFlow_matchesInteractor() = + testScope.runTest { + var latest: List<Int>? = null + val job = underTest.subscriptionIdsFlow.onEach { latest = it }.launchIn(this) + + interactor.filteredSubscriptions.value = + listOf( + SubscriptionModel(subscriptionId = 1, isOpportunistic = false), + ) + assertThat(latest).isEqualTo(listOf(1)) + + interactor.filteredSubscriptions.value = + listOf( + SubscriptionModel(subscriptionId = 2, isOpportunistic = false), + SubscriptionModel(subscriptionId = 5, isOpportunistic = true), + SubscriptionModel(subscriptionId = 7, isOpportunistic = true), + ) + assertThat(latest).isEqualTo(listOf(2, 5, 7)) + + interactor.filteredSubscriptions.value = emptyList() + assertThat(latest).isEmpty() + + job.cancel() + } + + @Test fun `caching - mobile icon view model is reused for same sub id`() = testScope.runTest { val model1 = underTest.viewModelForSub(1, StatusBarLocation.HOME) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java index 1eee08c22187..91c88cebff79 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java @@ -21,6 +21,7 @@ import static android.os.BatteryManager.EXTRA_PRESENT; import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker; +import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_QS; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; @@ -167,8 +168,10 @@ public class BatteryControllerTest extends SysuiTestCase { mBatteryController.setPowerSaveMode(false, mView); StaticInOrder inOrder = inOrder(staticMockMarker(BatterySaverUtils.class)); - inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true)); - inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true)); + inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), true, true, + SAVER_ENABLED_QS)); + inOrder.verify(() -> BatterySaverUtils.setPowerSaveMode(getContext(), false, true, + SAVER_ENABLED_QS)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java index 01e94baab7c3..391c8ca4d286 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java @@ -62,7 +62,7 @@ import android.window.OnBackInvokedDispatcher; import android.window.WindowOnBackInvokedDispatcher; import androidx.annotation.NonNull; -import androidx.core.animation.AnimatorTestRule2; +import androidx.core.animation.AnimatorTestRule; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; @@ -110,7 +110,7 @@ public class RemoteInputViewTest extends SysuiTestCase { private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake(); @ClassRule - public static AnimatorTestRule2 mAnimatorTestRule = new AnimatorTestRule2(); + public static AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(); @Before public void setUp() throws Exception { diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java new file mode 100644 index 000000000000..9cf3e443320d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java @@ -0,0 +1,91 @@ +/* + * 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.volume; + +import static android.media.AudioManager.CSD_WARNING_DOSE_REACHED_1X; +import static android.media.AudioManager.CSD_WARNING_DOSE_REPEATED_5X; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import android.app.Notification; +import android.app.NotificationManager; +import android.media.AudioManager; +import android.test.suitebuilder.annotation.SmallTest; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import com.android.internal.messages.nano.SystemMessageProto; +import com.android.systemui.SysuiTestCase; +import com.android.systemui.util.concurrency.FakeExecutor; +import com.android.systemui.util.time.FakeSystemClock; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class CsdWarningDialogTest extends SysuiTestCase { + + private NotificationManager mNotificationManager; + private AudioManager mAudioManager; + + @Before + public void setup() { + mNotificationManager = mock(NotificationManager.class); + getContext().addMockSystemService(NotificationManager.class, mNotificationManager); + + mAudioManager = mock(AudioManager.class); + getContext().addMockSystemService(AudioManager.class, mAudioManager); + } + + @Test + public void create1XCsdDialogAndWait_sendsNotification() { + FakeExecutor executor = new FakeExecutor(new FakeSystemClock()); + // instantiate directly instead of via factory; we don't want executor to be @Background + CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REACHED_1X, mContext, + mAudioManager, mNotificationManager, executor, null); + + dialog.show(); + executor.advanceClockToLast(); + executor.runAllReady(); + dialog.dismiss(); + + verify(mNotificationManager).notify( + eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class)); + } + + @Test + public void create5XCsdDiSalogAndWait_willNotSendNotification() { + FakeExecutor executor = new FakeExecutor(new FakeSystemClock()); + CsdWarningDialog dialog = new CsdWarningDialog(CSD_WARNING_DOSE_REPEATED_5X, mContext, + mAudioManager, mNotificationManager, executor, null); + + dialog.show(); + executor.advanceClockToLast(); + executor.runAllReady(); + dialog.dismiss(); + + verify(mNotificationManager, never()).notify( + eq(SystemMessageProto.SystemMessage.NOTE_CSD_LOWER_AUDIO), any(Notification.class)); + } +} 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 d419095921b8..eb2688894cb0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -102,6 +102,15 @@ public class VolumeDialogImplTest extends SysuiTestCase { InteractionJankMonitor mInteractionJankMonitor; @Mock private DumpManager mDumpManager; + @Mock CsdWarningDialog mCsdWarningDialog; + + private final CsdWarningDialog.Factory mCsdWarningDialogFactory = + new CsdWarningDialog.Factory() { + @Override + public CsdWarningDialog create(int warningType, Runnable onCleanup) { + return mCsdWarningDialog; + } + }; @Before public void setup() throws Exception { @@ -124,6 +133,7 @@ public class VolumeDialogImplTest extends SysuiTestCase { mInteractionJankMonitor, mDeviceConfigProxy, mExecutor, + mCsdWarningDialogFactory, mDumpManager ); mDialog.init(0, null); @@ -352,6 +362,14 @@ public class VolumeDialogImplTest extends SysuiTestCase { mDialog.getDialogView().animate().cancel(); } + @Test + public void showCsdWarning_dialogShown() { + mDialog.showCsdWarningH(AudioManager.CSD_WARNING_DOSE_REACHED_1X, + CsdWarningDialog.NO_ACTION_TIMEOUT_MS); + + verify(mCsdWarningDialog).show(); + } + /* @Test public void testContentDescriptions() { 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 new file mode 100644 index 000000000000..738f09ddce3d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt @@ -0,0 +1,63 @@ +/* + * 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 com.android.keyguard.FaceAuthUiEvent +import com.android.systemui.keyguard.shared.model.AuthenticationStatus +import com.android.systemui.keyguard.shared.model.DetectionStatus +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull + +class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository { + + override val isAuthenticated = MutableStateFlow(false) + override val canRunFaceAuth = MutableStateFlow(false) + private val _authenticationStatus = MutableStateFlow<AuthenticationStatus?>(null) + override val authenticationStatus: Flow<AuthenticationStatus> = + _authenticationStatus.filterNotNull() + fun setAuthenticationStatus(status: AuthenticationStatus) { + _authenticationStatus.value = status + } + private val _detectionStatus = MutableStateFlow<DetectionStatus?>(null) + override val detectionStatus: Flow<DetectionStatus> + get() = _detectionStatus.filterNotNull() + fun setDetectionStatus(status: DetectionStatus) { + _detectionStatus.value = status + } + override val isLockedOut = MutableStateFlow(false) + private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null) + val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> = + _runningAuthRequest.asStateFlow() + + 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/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/packages/overlays/Android.mk b/packages/overlays/Android.mk index cbca3f03fb0f..a41d0e57cd21 100644 --- a/packages/overlays/Android.mk +++ b/packages/overlays/Android.mk @@ -32,6 +32,7 @@ LOCAL_REQUIRED_MODULES := \ NavigationBarModeGesturalOverlayWideBack \ NavigationBarModeGesturalOverlayExtraWideBack \ TransparentNavigationBarOverlay \ + NotesRoleEnabledOverlay \ preinstalled-packages-platform-overlays.xml include $(BUILD_PHONY_PACKAGE) diff --git a/core/tests/expresslog/Android.bp b/packages/overlays/NotesRoleEnabledOverlay/Android.bp index cab49a76a734..68ebd9652399 100644 --- a/core/tests/expresslog/Android.bp +++ b/packages/overlays/NotesRoleEnabledOverlay/Android.bp @@ -1,16 +1,18 @@ -// Copyright (C) 2023 The Android Open Source Project +// +// 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 +// 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 { // See: http://go/android-license-faq @@ -21,27 +23,8 @@ package { default_applicable_licenses: ["frameworks_base_license"], } -android_test { - name: "ExpressLogTests", - - srcs: [ - "src/**/*.java", - ], - - static_libs: [ - "androidx.test.rules", - "modules-utils-build", - ], - - libs: [ - "android.test.base", - "android.test.runner", - ], - - platform_apis: true, - test_suites: [ - "general-tests", - ], - - certificate: "platform", +runtime_resource_overlay { + name: "NotesRoleEnabledOverlay", + theme: "NotesRoleEnabled", + product_specific: true, } diff --git a/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml b/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml new file mode 100644 index 000000000000..c01178d1727d --- /dev/null +++ b/packages/overlays/NotesRoleEnabledOverlay/AndroidManifest.xml @@ -0,0 +1,26 @@ +<!-- +/** + * 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. + */ +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.role.notes.enabled" + android:versionCode="1" + android:versionName="1.0"> + <overlay android:targetPackage="android" + android:priority="1"/> + + <application android:label="@string/notes_role_enabled_overlay_title" android:hasCode="false"/> +</manifest>
\ No newline at end of file diff --git a/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml b/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml new file mode 100644 index 000000000000..f27f5f42ae6b --- /dev/null +++ b/packages/overlays/NotesRoleEnabledOverlay/res/values/config.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2023, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources> + <!-- Whether the default notes role should be enabled. In builds without + device-specific overlays, this can be controlled in developer options. --> + <bool name="config_enableDefaultNotes">true</bool> + + <!-- Whether the default notes role for work profile should be enabled. + In builds without device-specific overlays, this can be controlled in + developer options. --> + <bool name="config_enableDefaultNotesForWorkProfile">true</bool> +</resources> diff --git a/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml b/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml new file mode 100644 index 000000000000..3edbb571c4d1 --- /dev/null +++ b/packages/overlays/NotesRoleEnabledOverlay/res/values/strings.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/** + * Copyright (c) 2019, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +--> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Name of overlay [CHAR LIMIT=64] --> + <string name="notes_role_enabled_overlay_title" translatable="false">Notes Role enabled</string> +</resources>
\ No newline at end of file diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto index 47027342974d..21d09792f1c7 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -402,5 +402,7 @@ message SystemMessage { // Package: android NOTE_ALL_MANAGED_SUBSCRIPTIONS_AND_MANAGED_PROFILE_OFF = 1006; + // Notify the user that audio was lowered based on Calculated Sound Dose (CSD) + NOTE_CSD_LOWER_AUDIO = 1007; } } diff --git a/services/Android.bp b/services/Android.bp index 6e6c55325e3d..b0a0e5e44a8c 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -112,6 +112,7 @@ filegroup { ":services.searchui-sources", ":services.selectiontoolbar-sources", ":services.smartspace-sources", + ":services.soundtrigger-sources", ":services.systemcaptions-sources", ":services.translation-sources", ":services.texttospeech-sources", @@ -169,6 +170,7 @@ java_library { "services.searchui", "services.selectiontoolbar", "services.smartspace", + "services.soundtrigger", "services.systemcaptions", "services.translation", "services.texttospeech", 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/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/Android.bp b/services/core/Android.bp index c8caab93d76c..cfdf3ac5915b 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -174,7 +174,6 @@ java_library_static { "android.hardware.configstore-V1.1-java", "android.hardware.ir-V1-java", "android.hardware.rebootescrow-V1-java", - "android.hardware.soundtrigger-V2.3-java", "android.hardware.power.stats-V2-java", "android.hardware.power-V4-java", "android.hidl.manager-V1.2-java", diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 3ecf93328219..8fc30e43d1a0 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -80,7 +80,7 @@ import android.util.apk.ApkSignatureVerifier; import android.util.apk.ApkSigningBlockUtils; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.expresslog.Histogram; +import com.android.modules.expresslog.Histogram; import com.android.internal.os.IBinaryTransparencyService; import com.android.internal.util.FrameworkStatsLog; import com.android.server.pm.ApexManager; diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index 92889c05d9f4..d256aead97e8 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -426,7 +426,7 @@ public class PackageWatchdog { } int impact = registeredObserver.onHealthCheckFailed( versionedPackage, failureReason, mitigationCount); - if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE + if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0 && impact < currentObserverImpact) { currentObserverToNotify = registeredObserver; currentObserverImpact = impact; @@ -466,7 +466,7 @@ public class PackageWatchdog { if (registeredObserver != null) { int impact = registeredObserver.onHealthCheckFailed( failingPackage, failureReason, 1); - if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE + if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0 && impact < currentObserverImpact) { currentObserverToNotify = registeredObserver; currentObserverImpact = impact; @@ -494,7 +494,7 @@ public class PackageWatchdog { PackageHealthObserver registeredObserver = observer.registeredObserver; if (registeredObserver != null) { int impact = registeredObserver.onBootLoop(mitigationCount); - if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE + if (impact != PackageHealthObserverImpact.USER_IMPACT_LEVEL_0 && impact < currentObserverImpact) { currentObserverToNotify = registeredObserver; currentObserverImpact = impact; @@ -576,19 +576,23 @@ public class PackageWatchdog { /** Possible severity values of the user impact of a {@link PackageHealthObserver#execute}. */ @Retention(SOURCE) - @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_NONE, - PackageHealthObserverImpact.USER_IMPACT_LOW, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM, - PackageHealthObserverImpact.USER_IMPACT_HIGH}) + @IntDef(value = {PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_50, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100}) public @interface PackageHealthObserverImpact { /** No action to take. */ - int USER_IMPACT_NONE = 0; + int USER_IMPACT_LEVEL_0 = 0; /* Action has low user impact, user of a device will barely notice. */ - int USER_IMPACT_LOW = 1; - /* Action has medium user impact, user of a device will likely notice. */ - int USER_IMPACT_MEDIUM = 3; + int USER_IMPACT_LEVEL_10 = 10; + /* Actions having medium user impact, user of a device will likely notice. */ + int USER_IMPACT_LEVEL_30 = 30; + int USER_IMPACT_LEVEL_50 = 50; + int USER_IMPACT_LEVEL_70 = 70; /* Action has high user impact, a last resort, user of a device will be very frustrated. */ - int USER_IMPACT_HIGH = 5; + int USER_IMPACT_LEVEL_100 = 100; } /** Register instances of this interface to receive notifications on package failure. */ @@ -633,7 +637,7 @@ public class PackageWatchdog { * boot loop (including this time). */ default @PackageHealthObserverImpact int onBootLoop(int mitigationCount) { - return PackageHealthObserverImpact.USER_IMPACT_NONE; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } /** diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java index 3de65f94decf..6e2e5f75b8f3 100644 --- a/services/core/java/com/android/server/RescueParty.java +++ b/services/core/java/com/android/server/RescueParty.java @@ -107,7 +107,7 @@ public class RescueParty { static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG = "namespace_to_package_mapping"; @VisibleForTesting - static final long FACTORY_RESET_THROTTLE_DURATION_MS = TimeUnit.MINUTES.toMillis(10); + static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 10; private static final String NAME = "rescue-party-observer"; @@ -117,6 +117,8 @@ public class RescueParty { "persist.device_config.configuration.disable_rescue_party"; private static final String PROP_DISABLE_FACTORY_RESET_FLAG = "persist.device_config.configuration.disable_rescue_party_factory_reset"; + private static final String PROP_THROTTLE_DURATION_MIN_FLAG = + "persist.device_config.configuration.rescue_party_throttle_duration_min"; private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; @@ -489,13 +491,14 @@ public class RescueParty { switch(rescueLevel) { case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS: case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES: - return PackageHealthObserverImpact.USER_IMPACT_LOW; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_10; case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS: case LEVEL_WARM_REBOOT: + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_50; case LEVEL_FACTORY_RESET: - return PackageHealthObserverImpact.USER_IMPACT_HIGH; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; default: - return PackageHealthObserverImpact.USER_IMPACT_NONE; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } } @@ -633,7 +636,7 @@ public class RescueParty { return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, mayPerformReboot(failedPackage))); } else { - return PackageHealthObserverImpact.USER_IMPACT_NONE; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } } @@ -677,7 +680,7 @@ public class RescueParty { @Override public int onBootLoop(int mitigationCount) { if (isDisabled()) { - return PackageHealthObserverImpact.USER_IMPACT_NONE; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } return mapRescueLevelToUserImpact(getRescueLevel(mitigationCount, true)); } @@ -721,7 +724,9 @@ public class RescueParty { private boolean shouldThrottleReboot() { Long lastResetTime = SystemProperties.getLong(PROP_LAST_FACTORY_RESET_TIME_MS, 0); long now = System.currentTimeMillis(); - return now < lastResetTime + FACTORY_RESET_THROTTLE_DURATION_MS; + long throttleDurationMin = SystemProperties.getLong(PROP_THROTTLE_DURATION_MIN_FLAG, + DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN); + return now < lastResetTime + TimeUnit.MINUTES.toMillis(throttleDurationMin); } private boolean isPersistentSystemApp(@NonNull String packageName) { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/core/java/com/android/server/SoundTriggerInternal.java index cc398d930c7e..e6c1750c4a1d 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java +++ b/services/core/java/com/android/server/SoundTriggerInternal.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.soundtrigger; +package com.android.server; import android.annotation.NonNull; import android.annotation.Nullable; @@ -29,15 +29,13 @@ import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.media.permission.Identity; import android.os.IBinder; -import com.android.server.voiceinteraction.VoiceInteractionManagerService; - import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; /** * Provides a local service for managing voice-related recoginition models. This is primarily used - * by the {@link VoiceInteractionManagerService}. + * by the {@code VoiceInteractionManagerService}. */ public interface SoundTriggerInternal { /** diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 7b618b11bd45..e248007eca19 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -26,6 +26,17 @@ import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; import static android.app.ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; import static android.app.ActivityManager.PROCESS_STATE_TOP; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE; import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DEPRECATED; import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_DISABLED; import static android.app.ForegroundServiceTypePolicy.FGS_TYPE_POLICY_CHECK_OK; @@ -117,6 +128,7 @@ import android.annotation.UptimeMillisLong; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.ActivityManagerInternal.OomAdjReason; import android.app.ActivityManagerInternal.ServiceNotificationPolicy; import android.app.ActivityThread; import android.app.AppGlobals; @@ -1146,7 +1158,7 @@ public final class ActiveServices { } finally { /* Will be a no-op if nothing pending */ mAm.updateOomAdjPendingTargetsLocked( - OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + OOM_ADJ_REASON_START_SERVICE); } } else { unbindServiceLocked(connection); @@ -1236,8 +1248,7 @@ public final class ActiveServices { /* ignore - local call */ } finally { /* Will be a no-op if nothing pending */ - mAm.updateOomAdjPendingTargetsLocked( - OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); } } else { // Starting a service try { @@ -1311,7 +1322,7 @@ public final class ActiveServices { false /* packageFrozen */, true /* enqueueOomAdj */); /* Will be a no-op if nothing pending */ - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); if (error != null) { return new ComponentName("!!", error); } @@ -1496,7 +1507,7 @@ public final class ActiveServices { stopServiceLocked(service, true); } if (size > 0) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UID_IDLE); } } } @@ -3296,7 +3307,7 @@ public final class ActiveServices { Slog.e(TAG_SERVICE, "Short FGS procstate demoted: " + sr); - mAm.updateOomAdjLocked(sr.app, OomAdjuster.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT); + mAm.updateOomAdjLocked(sr.app, OOM_ADJ_REASON_SHORT_FGS_TIMEOUT); } } @@ -3630,7 +3641,7 @@ public final class ActiveServices { needOomAdj = true; if (bringUpServiceLocked(s, service.getFlags(), callerFg, false, permissionsReviewRequired, packageFrozen, true) != null) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE); return 0; } } @@ -3655,7 +3666,7 @@ public final class ActiveServices { mAm.enqueueOomAdjTargetLocked(s.app); } if (needOomAdj) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_BIND_SERVICE); } final int packageState = wasStopped @@ -3787,7 +3798,8 @@ public final class ActiveServices { } } - serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false); + serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false, false, + OOM_ADJ_REASON_EXECUTING_SERVICE); } } finally { Binder.restoreCallingIdentity(origId); @@ -3878,7 +3890,7 @@ public final class ActiveServices { } } - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_UNBIND_SERVICE); } finally { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); @@ -3925,7 +3937,8 @@ public final class ActiveServices { } } - serviceDoneExecutingLocked(r, inDestroying, false, false); + serviceDoneExecutingLocked(r, inDestroying, false, false, + OOM_ADJ_REASON_UNBIND_SERVICE); } } finally { Binder.restoreCallingIdentity(origId); @@ -4360,11 +4373,11 @@ public final class ActiveServices { /** * Bump the given service record into executing state. * @param oomAdjReason The caller requests it to perform the oomAdjUpdate not {@link - * OomAdjuster#OOM_ADJ_REASON_NONE}. + * ActivityManagerInternal#OOM_ADJ_REASON_NONE}. * @return {@code true} if it performed oomAdjUpdate. */ private boolean bumpServiceExecutingLocked( - ServiceRecord r, boolean fg, String why, @OomAdjuster.OomAdjReason int oomAdjReason) { + ServiceRecord r, boolean fg, String why, @OomAdjReason int oomAdjReason) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, ">>> EXECUTING " + why + " of " + r + " in app " + r.app); else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG_SERVICE_EXECUTING, ">>> EXECUTING " @@ -4416,7 +4429,7 @@ public final class ActiveServices { } } boolean oomAdjusted = false; - if (oomAdjReason != OomAdjuster.OOM_ADJ_REASON_NONE && r.app != null + if (oomAdjReason != OOM_ADJ_REASON_NONE && r.app != null && r.app.mState.getCurProcState() > ActivityManager.PROCESS_STATE_SERVICE) { // Force an immediate oomAdjUpdate, so the client app could be in the correct process // state before doing any service related transactions @@ -4440,8 +4453,7 @@ public final class ActiveServices { + " rebind=" + rebind); if ((!i.requested || rebind) && i.apps.size() > 0) { try { - bumpServiceExecutingLocked(r, execInFg, "bind", - OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE); + bumpServiceExecutingLocked(r, execInFg, "bind", OOM_ADJ_REASON_BIND_SERVICE); if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { Trace.instant(Trace.TRACE_TAG_ACTIVITY_MANAGER, "requestServiceBinding=" + i.intent.getIntent() + ". bindSeq=" + mBindServiceSeqCounter); @@ -4457,13 +4469,15 @@ public final class ActiveServices { // Keep the executeNesting count accurate. if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e); final boolean inDestroying = mDestroyingServices.contains(r); - serviceDoneExecutingLocked(r, inDestroying, inDestroying, false); + serviceDoneExecutingLocked(r, inDestroying, inDestroying, false, + OOM_ADJ_REASON_UNBIND_SERVICE); throw e; } catch (RemoteException e) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r); // Keep the executeNesting count accurate. final boolean inDestroying = mDestroyingServices.contains(r); - serviceDoneExecutingLocked(r, inDestroying, inDestroying, false); + serviceDoneExecutingLocked(r, inDestroying, inDestroying, false, + OOM_ADJ_REASON_UNBIND_SERVICE); return false; } } @@ -4841,7 +4855,7 @@ public final class ActiveServices { // Ignore, it's been logged and nothing upstack cares. } finally { /* Will be a no-op if nothing pending */ - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); } } @@ -5193,13 +5207,14 @@ public final class ActiveServices { final ProcessServiceRecord psr = app.mServices; final boolean newService = psr.startService(r); - bumpServiceExecutingLocked(r, execInFg, "create", OomAdjuster.OOM_ADJ_REASON_NONE); + bumpServiceExecutingLocked(r, execInFg, "create", + OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */); mAm.updateLruProcessLocked(app, false, null); updateServiceForegroundLocked(psr, /* oomAdj= */ false); // Force an immediate oomAdjUpdate, so the client app could be in the correct process state // before doing any service related transactions mAm.enqueueOomAdjTargetLocked(app); - mAm.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + mAm.updateOomAdjLocked(app, OOM_ADJ_REASON_START_SERVICE); boolean created = false; try { @@ -5233,7 +5248,8 @@ public final class ActiveServices { if (!created) { // Keep the executeNesting count accurate. final boolean inDestroying = mDestroyingServices.contains(r); - serviceDoneExecutingLocked(r, inDestroying, inDestroying, false); + serviceDoneExecutingLocked(r, inDestroying, inDestroying, false, + OOM_ADJ_REASON_STOP_SERVICE); // Cleanup. if (newService) { @@ -5319,7 +5335,8 @@ public final class ActiveServices { mAm.grantImplicitAccess(r.userId, si.intent, si.callingId, UserHandle.getAppId(r.appInfo.uid) ); - bumpServiceExecutingLocked(r, execInFg, "start", OomAdjuster.OOM_ADJ_REASON_NONE); + bumpServiceExecutingLocked(r, execInFg, "start", + OOM_ADJ_REASON_NONE /* use "none" to avoid extra oom adj */); if (r.fgRequired && !r.fgWaiting) { if (!r.isForeground) { if (DEBUG_BACKGROUND_CHECK) { @@ -5345,7 +5362,7 @@ public final class ActiveServices { if (!oomAdjusted) { mAm.enqueueOomAdjTargetLocked(r.app); - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); } ParceledListSlice<ServiceStartArgs> slice = new ParceledListSlice<>(args); slice.setInlineCountLimit(4); @@ -5371,10 +5388,11 @@ public final class ActiveServices { // Keep nesting count correct final boolean inDestroying = mDestroyingServices.contains(r); for (int i = 0, size = args.size(); i < size; i++) { - serviceDoneExecutingLocked(r, inDestroying, inDestroying, true); + serviceDoneExecutingLocked(r, inDestroying, inDestroying, true, + OOM_ADJ_REASON_STOP_SERVICE); } /* Will be a no-op if nothing pending */ - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE); if (caughtException instanceof TransactionTooLargeException) { throw (TransactionTooLargeException)caughtException; } @@ -5461,7 +5479,7 @@ public final class ActiveServices { if (ibr.hasBound) { try { oomAdjusted |= bumpServiceExecutingLocked(r, false, "bring down unbind", - OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + OOM_ADJ_REASON_UNBIND_SERVICE); ibr.hasBound = false; ibr.requested = false; r.app.getThread().scheduleUnbindService(r, @@ -5615,7 +5633,7 @@ public final class ActiveServices { } else { try { oomAdjusted |= bumpServiceExecutingLocked(r, false, "destroy", - oomAdjusted ? 0 : OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + oomAdjusted ? 0 : OOM_ADJ_REASON_STOP_SERVICE); mDestroyingServices.add(r); r.destroying = true; r.app.getThread().scheduleStopService(r); @@ -5637,7 +5655,7 @@ public final class ActiveServices { if (!oomAdjusted) { mAm.enqueueOomAdjTargetLocked(r.app); if (!enqueueOomAdj) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE); } } if (r.bindings.size() > 0) { @@ -5762,8 +5780,7 @@ public final class ActiveServices { if (s.app != null && s.app.getThread() != null && b.intent.apps.size() == 0 && b.intent.hasBound) { try { - bumpServiceExecutingLocked(s, false, "unbind", - OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + bumpServiceExecutingLocked(s, false, "unbind", OOM_ADJ_REASON_UNBIND_SERVICE); if (b.client != s.app && c.notHasFlag(Context.BIND_WAIVE_PRIORITY) && s.app.mState.getSetProcState() <= PROCESS_STATE_HEAVY_WEIGHT) { // If this service's process is not already in the cached list, @@ -5886,7 +5903,8 @@ public final class ActiveServices { } } final long origId = Binder.clearCallingIdentity(); - serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj); + serviceDoneExecutingLocked(r, inDestroying, inDestroying, enqueueOomAdj, + OOM_ADJ_REASON_EXECUTING_SERVICE); Binder.restoreCallingIdentity(origId); } else { Slog.w(TAG, "Done executing unknown service from pid " @@ -5905,11 +5923,11 @@ public final class ActiveServices { r.tracker.setStarted(false, memFactor, now); } } - serviceDoneExecutingLocked(r, true, true, enqueueOomAdj); + serviceDoneExecutingLocked(r, true, true, enqueueOomAdj, OOM_ADJ_REASON_PROCESS_END); } private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, - boolean finishing, boolean enqueueOomAdj) { + boolean finishing, boolean enqueueOomAdj, @OomAdjReason int oomAdjReason) { if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "<<< DONE EXECUTING " + r + ": nesting=" + r.executeNesting + ", inDestroying=" + inDestroying + ", app=" + r.app); @@ -5945,7 +5963,7 @@ public final class ActiveServices { if (enqueueOomAdj) { mAm.enqueueOomAdjTargetLocked(r.app); } else { - mAm.updateOomAdjLocked(r.app, OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjLocked(r.app, oomAdjReason); } } r.executeFg = false; @@ -6015,7 +6033,7 @@ public final class ActiveServices { bringDownServiceLocked(sr, true); } /* Will be a no-op if nothing pending */ - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_START_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_SERVICE); } } catch (RemoteException e) { Slog.w(TAG, "Exception in new application when starting service " @@ -6075,7 +6093,7 @@ public final class ActiveServices { } } if (needOomAdj) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_PROCESS_END); } } @@ -6146,7 +6164,7 @@ public final class ActiveServices { bringDownServiceLocked(mTmpCollectionResults.get(i), true); } if (size > 0) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_COMPONENT_DISABLED); } if (fullStop && !mTmpCollectionResults.isEmpty()) { // if we're tearing down the app's entire service state, account for possible @@ -6273,7 +6291,7 @@ public final class ActiveServices { } } if (needOomAdj) { - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_REMOVE_TASK); } } @@ -6444,7 +6462,7 @@ public final class ActiveServices { } } - mAm.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE); + mAm.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_STOP_SERVICE); if (!allowRestart) { psr.stopAllServices(); 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 9752ade1ce34..b32f8c973e6a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -44,6 +44,14 @@ import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED; import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_DESTROYED; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_NONE; import static android.content.pm.ApplicationInfo.HIDDEN_API_ENFORCEMENT_DEFAULT; @@ -199,6 +207,7 @@ import android.app.ActivityManagerInternal.BindServiceEventListener; import android.app.ActivityManagerInternal.BroadcastEventListener; import android.app.ActivityManagerInternal.ForegroundServiceStateListener; import android.app.ActivityManagerInternal.MediaProjectionTokenEvent; +import android.app.ActivityManagerInternal.OomAdjReason; import android.app.ActivityTaskManager.RootTaskInfo; import android.app.ActivityThread; import android.app.AnrController; @@ -368,7 +377,6 @@ import android.util.FeatureFlagUtils; import android.util.IndentingPrintWriter; import android.util.IntArray; import android.util.Log; -import android.util.LogWriter; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Slog; @@ -1968,7 +1976,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_SYSTEM); addPidLocked(app); updateLruProcessLocked(app, false, null); - updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT); } } catch (PackageManager.NameNotFoundException e) { throw new RuntimeException( @@ -2502,7 +2510,7 @@ public class ActivityManagerService extends IActivityManager.Stub // bind background threads to little cores // this is expected to fail inside of framework tests because apps can't touch cpusets directly // make sure we've already adjusted system_server's internal view of itself first - updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + updateOomAdjLocked(OOM_ADJ_REASON_SYSTEM_INIT); try { Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(), Process.THREAD_GROUP_SYSTEM); @@ -3387,7 +3395,7 @@ public class ActivityManagerService extends IActivityManager.Stub handleAppDiedLocked(app, pid, false, true, fromBinderDied); if (doOomAdj) { - updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END); + updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END); } if (doLowMem) { mAppProfiler.doLowMemReportIfNeededLocked(app); @@ -4843,7 +4851,7 @@ public class ActivityManagerService extends IActivityManager.Stub } if (!didSomething) { - updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN); + updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN); checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked"); } @@ -5485,7 +5493,7 @@ public class ActivityManagerService extends IActivityManager.Stub "setProcessLimit()"); synchronized (this) { mConstants.setOverrideMaxCachedProcesses(max); - trimApplicationsLocked(true, OomAdjuster.OOM_ADJ_REASON_PROCESS_END); + trimApplicationsLocked(true, OOM_ADJ_REASON_PROCESS_END); } } @@ -5513,7 +5521,7 @@ public class ActivityManagerService extends IActivityManager.Stub pr.mState.setForcingToImportant(null); clearProcessForegroundLocked(pr); } - updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY); } } @@ -5560,7 +5568,7 @@ public class ActivityManagerService extends IActivityManager.Stub } if (changed) { - updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY); } } } @@ -6869,7 +6877,7 @@ public class ActivityManagerService extends IActivityManager.Stub new HostingRecord(HostingRecord.HOSTING_TYPE_ADDED_APPLICATION, customProcess != null ? customProcess : info.processName)); updateLruProcessLocked(app, false, null); - updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN); + updateOomAdjLocked(app, OOM_ADJ_REASON_PROCESS_BEGIN); } // Report usage as process is persistent and being started. @@ -6986,7 +6994,7 @@ public class ActivityManagerService extends IActivityManager.Stub mOomAdjProfiler.onWakefulnessChanged(wakefulness); mOomAdjuster.onWakefulnessChanged(wakefulness); - updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + updateOomAdjLocked(OOM_ADJ_REASON_UI_VISIBILITY); } } } @@ -7748,7 +7756,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } if (changed) { - updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY); } } } finally { @@ -8728,7 +8736,9 @@ public class ActivityManagerService extends IActivityManager.Stub // 'recoverable' is that the app doesn't crash). Normally, for nonrecoreable native crashes, // debuggerd will terminate the process, but there's a backup where ActivityManager will // also kill it. Avoid that. - if (!recoverable) { + if (recoverable) { + mAppErrors.sendRecoverableCrashToAppExitInfo(r, crashInfo); + } else { mAppErrors.crashApplication(r, crashInfo); } } @@ -9508,7 +9518,7 @@ public class ActivityManagerService extends IActivityManager.Stub mAppProfiler.setMemFactorOverrideLocked(level); // Kick off an oom adj update since we forced a mem factor update. - updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + updateOomAdjLocked(OOM_ADJ_REASON_SHELL); } } @@ -13408,7 +13418,7 @@ public class ActivityManagerService extends IActivityManager.Stub proc.mProfile.addHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP); // Try not to kill the process during backup - updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE); + updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP); // If the process is already attached, schedule the creation of the backup agent now. // If it is not yet live, this will be done when it attaches to the framework. @@ -13532,7 +13542,7 @@ public class ActivityManagerService extends IActivityManager.Stub // Not backing this app up any more; reset its OOM adjustment final ProcessRecord proc = backupTarget.app; - updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_NONE); + updateOomAdjLocked(proc, OOM_ADJ_REASON_BACKUP); proc.setInFullBackup(false); proc.mProfile.clearHostingComponentType(HOSTING_COMPONENT_TYPE_BACKUP); @@ -13923,7 +13933,7 @@ public class ActivityManagerService extends IActivityManager.Stub // If we actually concluded any broadcasts, we might now be able // to trim the recipients' apps from our working set if (doTrim) { - trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER); + trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER); return; } } @@ -15185,7 +15195,7 @@ public class ActivityManagerService extends IActivityManager.Stub queue.finishReceiverLocked(callerApp, resultCode, resultData, resultExtras, resultAbort, true); // updateOomAdjLocked() will be done here - trimApplicationsLocked(false, OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER); + trimApplicationsLocked(false, OOM_ADJ_REASON_FINISH_RECEIVER); } } finally { @@ -16130,7 +16140,7 @@ public class ActivityManagerService extends IActivityManager.Stub item.foregroundServiceTypes = fgServiceTypes; } if (oomAdj) { - updateOomAdjLocked(proc, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + updateOomAdjLocked(proc, OOM_ADJ_REASON_UI_VISIBILITY); } } @@ -16196,7 +16206,7 @@ public class ActivityManagerService extends IActivityManager.Stub * {@link #enqueueOomAdjTargetLocked}. */ @GuardedBy("this") - void updateOomAdjPendingTargetsLocked(@OomAdjuster.OomAdjReason int oomAdjReason) { + void updateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) { mOomAdjuster.updateOomAdjPendingTargetsLocked(oomAdjReason); } @@ -16215,7 +16225,7 @@ public class ActivityManagerService extends IActivityManager.Stub } @GuardedBy("this") - final void updateOomAdjLocked(@OomAdjuster.OomAdjReason int oomAdjReason) { + final void updateOomAdjLocked(@OomAdjReason int oomAdjReason) { mOomAdjuster.updateOomAdjLocked(oomAdjReason); } @@ -16227,8 +16237,7 @@ public class ActivityManagerService extends IActivityManager.Stub * @return whether updateOomAdjLocked(app) was successful. */ @GuardedBy("this") - final boolean updateOomAdjLocked( - ProcessRecord app, @OomAdjuster.OomAdjReason int oomAdjReason) { + final boolean updateOomAdjLocked(ProcessRecord app, @OomAdjReason int oomAdjReason) { return mOomAdjuster.updateOomAdjLocked(app, oomAdjReason); } @@ -16461,16 +16470,14 @@ public class ActivityManagerService extends IActivityManager.Stub mOomAdjuster.setUidTempAllowlistStateLSP(uid, onAllowlist); } - private void trimApplications( - boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) { + private void trimApplications(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) { synchronized (this) { trimApplicationsLocked(forceFullOomAdj, oomAdjReason); } } @GuardedBy("this") - private void trimApplicationsLocked( - boolean forceFullOomAdj, @OomAdjuster.OomAdjReason int oomAdjReason) { + private void trimApplicationsLocked(boolean forceFullOomAdj, @OomAdjReason int oomAdjReason) { // First remove any unused application processes whose package // has been removed. boolean didSomething = false; @@ -17442,7 +17449,7 @@ public class ActivityManagerService extends IActivityManager.Stub } pr.mState.setHasOverlayUi(hasOverlayUi); //Slog.i(TAG, "Setting hasOverlayUi=" + pr.hasOverlayUi + " for pid=" + pid); - updateOomAdjLocked(pr, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + updateOomAdjLocked(pr, OOM_ADJ_REASON_UI_VISIBILITY); } } @@ -17577,7 +17584,7 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void trimApplications() { - ActivityManagerService.this.trimApplications(true, OomAdjuster.OOM_ADJ_REASON_ACTIVITY); + ActivityManagerService.this.trimApplications(true, OOM_ADJ_REASON_ACTIVITY); } public void killProcessesForRemovedTask(ArrayList<Object> procsToKill) { @@ -17626,9 +17633,9 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void updateOomAdj() { + public void updateOomAdj(@OomAdjReason int oomAdjReason) { synchronized (ActivityManagerService.this) { - ActivityManagerService.this.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + ActivityManagerService.this.updateOomAdjLocked(oomAdjReason); } } @@ -18288,8 +18295,7 @@ public class ActivityManagerService extends IActivityManager.Stub // sends to the activity. After this race issue between WM/ATMS and AMS is solved, this // workaround can be removed. (b/213288355) if (isNewPending) { - mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid, - OomAdjuster.OOM_ADJ_REASON_ACTIVITY); + mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid, OOM_ADJ_REASON_ACTIVITY); } // We need to update the network rules for the app coming to the top state so that // it can access network when the device or the app is in a restricted state diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index c343ec24412a..061bcd740f6b 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -555,6 +555,15 @@ class AppErrors { } } + void sendRecoverableCrashToAppExitInfo( + ProcessRecord r, ApplicationErrorReport.CrashInfo crashInfo) { + if (r == null || crashInfo == null + || !"Native crash".equals(crashInfo.exceptionClassName)) return; + synchronized (mService) { + mService.mProcessList.noteAppRecoverableCrash(r); + } + } + /** * Bring up the "unexpected error" dialog box for a crashing app. * Deal with edge cases (intercepts from instrumented applications, diff --git a/services/core/java/com/android/server/am/AppExitInfoTracker.java b/services/core/java/com/android/server/am/AppExitInfoTracker.java index 44436369fb31..4c0dd115a3cc 100644 --- a/services/core/java/com/android/server/am/AppExitInfoTracker.java +++ b/services/core/java/com/android/server/am/AppExitInfoTracker.java @@ -308,6 +308,16 @@ public final class AppExitInfoTracker { mKillHandler.obtainMessage(KillHandler.MSG_APP_KILL, raw).sendToTarget(); } + void scheduleNoteAppRecoverableCrash(final ProcessRecord app) { + if (!mAppExitInfoLoaded.get() || app == null || app.info == null) return; + + ApplicationExitInfo raw = obtainRawRecord(app, System.currentTimeMillis()); + raw.setReason(ApplicationExitInfo.REASON_CRASH_NATIVE); + raw.setSubReason(ApplicationExitInfo.SUBREASON_UNKNOWN); + raw.setDescription("recoverable_crash"); + mKillHandler.obtainMessage(KillHandler.MSG_APP_RECOVERABLE_CRASH, raw).sendToTarget(); + } + void scheduleNoteAppKill(final int pid, final int uid, final @Reason int reason, final @SubReason int subReason, final String msg) { if (!mAppExitInfoLoaded.get()) { @@ -421,8 +431,24 @@ public final class AppExitInfoTracker { scheduleLogToStatsdLocked(info, true); } + /** + * Make note when ActivityManagerService gets a recoverable native crash, as the process isn't + * being killed but the crash should still be added to AppExitInfo. Also, because we're not + * crashing, don't log out to statsd. + */ + @VisibleForTesting + @GuardedBy("mLock") + void handleNoteAppRecoverableCrashLocked(final ApplicationExitInfo raw) { + addExitInfoLocked(raw, /* recoverable */ true); + } + @GuardedBy("mLock") private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw) { + return addExitInfoLocked(raw, /* recoverable */ false); + } + + @GuardedBy("mLock") + private ApplicationExitInfo addExitInfoLocked(ApplicationExitInfo raw, boolean recoverable) { if (!mAppExitInfoLoaded.get()) { Slog.w(TAG, "Skipping saving the exit info due to ongoing loading from storage"); return null; @@ -438,7 +464,7 @@ public final class AppExitInfoTracker { } } for (int i = 0; i < packages.length; i++) { - addExitInfoInnerLocked(packages[i], uid, info); + addExitInfoInnerLocked(packages[i], uid, info, recoverable); } schedulePersistProcessExitInfo(false); @@ -845,7 +871,8 @@ public final class AppExitInfoTracker { } @GuardedBy("mLock") - private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info) { + private void addExitInfoInnerLocked(String packageName, int uid, ApplicationExitInfo info, + boolean recoverable) { AppExitInfoContainer container = mData.get(packageName, uid); if (container == null) { container = new AppExitInfoContainer(mAppExitInfoHistoryListSize); @@ -859,7 +886,11 @@ public final class AppExitInfoTracker { } mData.put(packageName, uid, container); } - container.addExitInfoLocked(info); + if (recoverable) { + container.addRecoverableCrashLocked(info); + } else { + container.addExitInfoLocked(info); + } } @GuardedBy("mLock") @@ -1284,38 +1315,40 @@ public final class AppExitInfoTracker { * A container class of {@link android.app.ApplicationExitInfo} */ final class AppExitInfoContainer { - private SparseArray<ApplicationExitInfo> mInfos; // index is pid + private SparseArray<ApplicationExitInfo> mInfos; // index is a pid + private SparseArray<ApplicationExitInfo> mRecoverableCrashes; // index is a pid private int mMaxCapacity; private int mUid; // Application uid, not isolated uid. AppExitInfoContainer(final int maxCapacity) { mInfos = new SparseArray<ApplicationExitInfo>(); + mRecoverableCrashes = new SparseArray<ApplicationExitInfo>(); mMaxCapacity = maxCapacity; } @GuardedBy("mLock") - void getExitInfoLocked(final int filterPid, final int maxNum, - ArrayList<ApplicationExitInfo> results) { + void getInfosLocked(SparseArray<ApplicationExitInfo> map, final int filterPid, + final int maxNum, ArrayList<ApplicationExitInfo> results) { if (filterPid > 0) { - ApplicationExitInfo r = mInfos.get(filterPid); + ApplicationExitInfo r = map.get(filterPid); if (r != null) { results.add(r); } } else { - final int numRep = mInfos.size(); + final int numRep = map.size(); if (maxNum <= 0 || numRep <= maxNum) { // Return all records. for (int i = 0; i < numRep; i++) { - results.add(mInfos.valueAt(i)); + results.add(map.valueAt(i)); } Collections.sort(results, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp())); } else { if (maxNum == 1) { // Most of the caller might be only interested with the most recent one - ApplicationExitInfo r = mInfos.valueAt(0); + ApplicationExitInfo r = map.valueAt(0); for (int i = 1; i < numRep; i++) { - ApplicationExitInfo t = mInfos.valueAt(i); + ApplicationExitInfo t = map.valueAt(i); if (r.getTimestamp() < t.getTimestamp()) { r = t; } @@ -1326,7 +1359,7 @@ public final class AppExitInfoTracker { ArrayList<ApplicationExitInfo> list = mTmpInfoList2; list.clear(); for (int i = 0; i < numRep; i++) { - list.add(mInfos.valueAt(i)); + list.add(map.valueAt(i)); } Collections.sort(list, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp())); @@ -1340,24 +1373,30 @@ public final class AppExitInfoTracker { } @GuardedBy("mLock") - void addExitInfoLocked(ApplicationExitInfo info) { + void getExitInfoLocked(final int filterPid, final int maxNum, + ArrayList<ApplicationExitInfo> results) { + getInfosLocked(mInfos, filterPid, maxNum, results); + } + + @GuardedBy("mLock") + void addInfoLocked(SparseArray<ApplicationExitInfo> map, ApplicationExitInfo info) { int size; - if ((size = mInfos.size()) >= mMaxCapacity) { + if ((size = map.size()) >= mMaxCapacity) { int oldestIndex = -1; long oldestTimeStamp = Long.MAX_VALUE; for (int i = 0; i < size; i++) { - ApplicationExitInfo r = mInfos.valueAt(i); + ApplicationExitInfo r = map.valueAt(i); if (r.getTimestamp() < oldestTimeStamp) { oldestTimeStamp = r.getTimestamp(); oldestIndex = i; } } if (oldestIndex >= 0) { - final File traceFile = mInfos.valueAt(oldestIndex).getTraceFile(); + final File traceFile = map.valueAt(oldestIndex).getTraceFile(); if (traceFile != null) { traceFile.delete(); } - mInfos.removeAt(oldestIndex); + map.removeAt(oldestIndex); } } // Claim the state information if there is any @@ -1367,7 +1406,17 @@ public final class AppExitInfoTracker { mActiveAppStateSummary, uid, pid)); info.setTraceFile(findAndRemoveFromSparse2dArray(mActiveAppTraces, uid, pid)); info.setAppTraceRetriever(mAppTraceRetriever); - mInfos.append(pid, info); + map.append(pid, info); + } + + @GuardedBy("mLock") + void addExitInfoLocked(ApplicationExitInfo info) { + addInfoLocked(mInfos, info); + } + + @GuardedBy("mLock") + void addRecoverableCrashLocked(ApplicationExitInfo info) { + addInfoLocked(mRecoverableCrashes, info); } @GuardedBy("mLock") @@ -1382,9 +1431,9 @@ public final class AppExitInfoTracker { } @GuardedBy("mLock") - void destroyLocked() { - for (int i = mInfos.size() - 1; i >= 0; i--) { - ApplicationExitInfo ai = mInfos.valueAt(i); + void destroyLocked(SparseArray<ApplicationExitInfo> map) { + for (int i = map.size() - 1; i >= 0; i--) { + ApplicationExitInfo ai = map.valueAt(i); final File traceFile = ai.getTraceFile(); if (traceFile != null) { traceFile.delete(); @@ -1395,24 +1444,37 @@ public final class AppExitInfoTracker { } @GuardedBy("mLock") + void destroyLocked() { + destroyLocked(mInfos); + destroyLocked(mRecoverableCrashes); + } + + @GuardedBy("mLock") void forEachRecordLocked(final BiFunction<Integer, ApplicationExitInfo, Integer> callback) { - if (callback != null) { - for (int i = mInfos.size() - 1; i >= 0; i--) { - switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) { - case FOREACH_ACTION_REMOVE_ITEM: - final File traceFile = mInfos.valueAt(i).getTraceFile(); - if (traceFile != null) { - traceFile.delete(); - } - mInfos.removeAt(i); - break; - case FOREACH_ACTION_STOP_ITERATION: - i = 0; - break; - case FOREACH_ACTION_NONE: - default: - break; - } + if (callback == null) return; + for (int i = mInfos.size() - 1; i >= 0; i--) { + switch (callback.apply(mInfos.keyAt(i), mInfos.valueAt(i))) { + case FOREACH_ACTION_STOP_ITERATION: return; + case FOREACH_ACTION_REMOVE_ITEM: + final File traceFile = mInfos.valueAt(i).getTraceFile(); + if (traceFile != null) { + traceFile.delete(); + } + mInfos.removeAt(i); + break; + } + } + for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) { + switch (callback.apply( + mRecoverableCrashes.keyAt(i), mRecoverableCrashes.valueAt(i))) { + case FOREACH_ACTION_STOP_ITERATION: return; + case FOREACH_ACTION_REMOVE_ITEM: + final File traceFile = mRecoverableCrashes.valueAt(i).getTraceFile(); + if (traceFile != null) { + traceFile.delete(); + } + mRecoverableCrashes.removeAt(i); + break; } } } @@ -1423,6 +1485,9 @@ public final class AppExitInfoTracker { for (int i = mInfos.size() - 1; i >= 0; i--) { list.add(mInfos.valueAt(i)); } + for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) { + list.add(mRecoverableCrashes.valueAt(i)); + } Collections.sort(list, (a, b) -> Long.compare(b.getTimestamp(), a.getTimestamp())); int size = list.size(); for (int i = 0; i < size; i++) { @@ -1434,10 +1499,13 @@ public final class AppExitInfoTracker { void writeToProto(ProtoOutputStream proto, long fieldId) { long token = proto.start(fieldId); proto.write(AppsExitInfoProto.Package.User.UID, mUid); - int size = mInfos.size(); - for (int i = 0; i < size; i++) { + for (int i = 0; i < mInfos.size(); i++) { mInfos.valueAt(i).writeToProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO); } + for (int i = 0; i < mRecoverableCrashes.size(); i++) { + mRecoverableCrashes.valueAt(i).writeToProto( + proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH); + } proto.end(token); } @@ -1448,14 +1516,23 @@ public final class AppExitInfoTracker { next != ProtoInputStream.NO_MORE_FIELDS; next = proto.nextField()) { switch (next) { - case (int) AppsExitInfoProto.Package.User.UID: + case (int) AppsExitInfoProto.Package.User.UID: { mUid = proto.readInt(AppsExitInfoProto.Package.User.UID); break; - case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: + } + case (int) AppsExitInfoProto.Package.User.APP_EXIT_INFO: { ApplicationExitInfo info = new ApplicationExitInfo(); info.readFromProto(proto, AppsExitInfoProto.Package.User.APP_EXIT_INFO); mInfos.put(info.getPid(), info); break; + } + case (int) AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH: { + ApplicationExitInfo info = new ApplicationExitInfo(); + info.readFromProto( + proto, AppsExitInfoProto.Package.User.APP_RECOVERABLE_CRASH); + mRecoverableCrashes.put(info.getPid(), info); + break; + } } } proto.end(token); @@ -1472,6 +1549,11 @@ public final class AppExitInfoTracker { list.add(mInfos.valueAt(i)); } } + for (int i = mRecoverableCrashes.size() - 1; i >= 0; i--) { + if (filterPid == 0 || filterPid == mRecoverableCrashes.keyAt(i)) { + list.add(mRecoverableCrashes.valueAt(i)); + } + } return list; } } @@ -1610,6 +1692,7 @@ public final class AppExitInfoTracker { static final int MSG_PROC_DIED = 4103; static final int MSG_APP_KILL = 4104; static final int MSG_STATSD_LOG = 4105; + static final int MSG_APP_RECOVERABLE_CRASH = 4106; KillHandler(Looper looper) { super(looper, null, true); @@ -1648,6 +1731,14 @@ public final class AppExitInfoTracker { } } break; + case MSG_APP_RECOVERABLE_CRASH: { + ApplicationExitInfo raw = (ApplicationExitInfo) msg.obj; + synchronized (mLock) { + handleNoteAppRecoverableCrashLocked(raw); + } + recycleRawRecord(raw); + } + break; default: super.handleMessage(msg); } diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java index bfc8251d97bb..0767218ec065 100644 --- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java +++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java @@ -18,9 +18,9 @@ package com.android.server.am; import static com.android.internal.util.Preconditions.checkState; import static com.android.server.am.BroadcastRecord.deliveryStateToString; -import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal; import static com.android.server.am.BroadcastRecord.isReceiverEquals; +import android.annotation.CheckResult; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -188,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; @@ -236,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); @@ -341,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; @@ -354,6 +373,7 @@ class BroadcastProcessQueue { return didSomething; } + @CheckResult private boolean forEachMatchingBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue, @NonNull BroadcastPredicate predicate, @NonNull BroadcastConsumer consumer, boolean andRemove) { @@ -370,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; } @@ -381,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; } } @@ -415,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; } } @@ -426,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; } } @@ -649,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; + } } /** @@ -709,7 +765,7 @@ class BroadcastProcessQueue { || consecutiveHighPriorityCount >= maxHighPriorityDispatchLimit); final boolean isLPQueueEligible = shouldConsiderLPQueue && nextLPRecord.enqueueTime <= nextHPRecord.enqueueTime - && !blockedOnOrderedDispatch(nextLPRecord, nextLPRecordIndex); + && !nextLPRecord.isBlocked(nextLPRecordIndex); return isLPQueueEligible ? lowPriorityQueue : highPriorityQueue; } @@ -722,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,39 +979,20 @@ class BroadcastProcessQueue { } } - private boolean blockedOnOrderedDispatch(BroadcastRecord r, int index) { - final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index]; - - int existingDeferredCount = 0; - if (r.deferUntilActive) { - for (int i = 0; i < index; i++) { - if (r.deferredUntilActive[i]) existingDeferredCount++; - } - } - - // We might be blocked waiting for other receivers to finish, - // typically for an ordered broadcast or priority traunches - if ((r.terminalCount + existingDeferredCount) < blockedUntilTerminalCount - && !isDeliveryStateTerminal(r.getDeliveryState(index))) { - return true; - } - return false; - } - /** - * Update {@link #getRunnableAt()} if it's currently invalidated. + * Update {@link #getRunnableAt()}, when needed. */ - private void updateRunnableAt() { - final SomeArgs next = peekNextBroadcast(); + void updateRunnableAt() { + if (!mRunnableAtInvalidated) return; mRunnableAtInvalidated = false; + + final SomeArgs next = peekNextBroadcast(); if (next != null) { final BroadcastRecord r = (BroadcastRecord) next.arg1; final int index = next.argi1; final long runnableAt = r.enqueueTime; - // If we're specifically queued behind other ordered dispatch activity, - // we aren't runnable yet - if (blockedOnOrderedDispatch(r, index)) { + if (r.isBlocked(index)) { mRunnableAt = Long.MAX_VALUE; mRunnableAtReason = REASON_BLOCKED; return; @@ -1047,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); @@ -1153,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(); @@ -1262,12 +1355,12 @@ class BroadcastProcessQueue { pw.print(info.activityInfo.name); } pw.println(); - final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex]; - if (blockedUntilTerminalCount != -1) { + final int blockedUntilBeyondCount = record.blockedUntilBeyondCount[recordIndex]; + if (blockedUntilBeyondCount != -1) { pw.print(" blocked until "); - pw.print(blockedUntilTerminalCount); + pw.print(blockedUntilBeyondCount); pw.print(", currently at "); - pw.print(record.terminalCount); + pw.print(record.beyondCount); pw.print(" of "); pw.println(record.receivers.size()); } diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java index bd36c3ff6f98..5a4d315767ca 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java @@ -17,6 +17,7 @@ package com.android.server.am; import static android.app.ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER; import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY; import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE; import static android.text.TextUtils.formatSimple; @@ -37,7 +38,6 @@ import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_L import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST; import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU; -import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java index a4bdf61e628f..8735f8a37b8b 100644 --- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java +++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java @@ -16,6 +16,7 @@ package com.android.server.am; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER; import static android.os.Process.ZYGOTE_POLICY_FLAG_EMPTY; import static android.os.Process.ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE; @@ -38,7 +39,6 @@ import static com.android.server.am.BroadcastRecord.getReceiverPackageName; import static com.android.server.am.BroadcastRecord.getReceiverProcessName; import static com.android.server.am.BroadcastRecord.getReceiverUid; import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal; -import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_START_RECEIVER; import android.annotation.NonNull; import android.annotation.Nullable; @@ -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(); } @@ -1008,6 +1008,7 @@ class BroadcastQueueModernImpl extends BroadcastQueue { } final BroadcastRecord r = queue.getActive(); + final int index = queue.getActiveIndex(); if (r.ordered) { r.resultCode = resultCode; r.resultData = resultData; @@ -1015,18 +1016,24 @@ class BroadcastQueueModernImpl extends BroadcastQueue { if (!r.isNoAbort()) { r.resultAbort = resultAbort; } + } - // When the caller aborted an ordered broadcast, we mark all - // remaining receivers as skipped - if (r.resultAbort) { - for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) { - setDeliveryState(null, null, r, i, r.receivers.get(i), - BroadcastRecord.DELIVERY_SKIPPED, "resultAbort"); - } + // To ensure that "beyond" high-water marks are updated in a monotonic + // way, we finish this receiver before possibly skipping any remaining + // aborted receivers + final boolean res = finishReceiverActiveLocked(queue, + BroadcastRecord.DELIVERY_DELIVERED, "remote app"); + + // When the caller aborted an ordered broadcast, we mark all + // remaining receivers as skipped + if (r.resultAbort) { + for (int i = index + 1; i < r.receivers.size(); i++) { + setDeliveryState(null, null, r, i, r.receivers.get(i), + BroadcastRecord.DELIVERY_SKIPPED, "resultAbort"); } } - return finishReceiverActiveLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app"); + return res; } /** @@ -1108,21 +1115,10 @@ class BroadcastQueueModernImpl extends BroadcastQueue { @NonNull Object receiver, @DeliveryState int newDeliveryState, @NonNull String reason) { final int cookie = traceBegin("setDeliveryState"); + + // Remember the old state and apply the new state final int oldDeliveryState = getDeliveryState(r, index); - boolean checkFinished = false; - - // Only apply state when we haven't already reached a terminal state; - // this is how we ignore racing timeout messages - if (!isDeliveryStateTerminal(oldDeliveryState)) { - r.setDeliveryState(index, newDeliveryState, reason); - if (oldDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) { - r.deferredCount--; - } else if (newDeliveryState == BroadcastRecord.DELIVERY_DEFERRED) { - // If we're deferring a broadcast, maybe that's enough to unblock the final callback - r.deferredCount++; - checkFinished = true; - } - } + final boolean beyondCountChanged = r.setDeliveryState(index, newDeliveryState, reason); // Emit any relevant tracing results when we're changing the delivery // state as part of running from a queue @@ -1147,15 +1143,13 @@ class BroadcastQueueModernImpl extends BroadcastQueue { + deliveryStateToString(newDeliveryState) + " because " + reason); } - r.terminalCount++; notifyFinishReceiver(queue, app, r, index, receiver); - checkFinished = true; } - // When entire ordered broadcast finished, deliver final result - if (checkFinished) { - final boolean recordFinished = - ((r.terminalCount + r.deferredCount) == r.receivers.size()); - if (recordFinished) { + + // When we've reached a new high-water mark, we might be in a position + // to unblock other receivers or the final resultTo + if (beyondCountChanged) { + if (r.beyondCount == r.receivers.size()) { scheduleResultTo(r); } @@ -1255,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"); }; /** @@ -1278,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; @@ -1315,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 @@ -1365,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(); @@ -1535,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/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java index c368290386a0..64fe39314f0e 100644 --- a/services/core/java/com/android/server/am/BroadcastRecord.java +++ b/services/core/java/com/android/server/am/BroadcastRecord.java @@ -24,6 +24,7 @@ import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROA import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_TARGET_T_ONLY; +import android.annotation.CheckResult; import android.annotation.CurrentTimeMillisLong; import android.annotation.ElapsedRealtimeLong; import android.annotation.IntDef; @@ -101,8 +102,7 @@ final class BroadcastRecord extends Binder { final @NonNull List<Object> receivers; // contains BroadcastFilter and ResolveInfo final @DeliveryState int[] delivery; // delivery state of each receiver final @NonNull String[] deliveryReasons; // reasons for delivery state of each receiver - final boolean[] deferredUntilActive; // whether each receiver is infinitely deferred - final int[] blockedUntilTerminalCount; // blocked until count of each receiver + final int[] blockedUntilBeyondCount; // blocked until count of each receiver @Nullable ProcessRecord resultToApp; // who receives final result if non-null @Nullable IIntentReceiver resultTo; // who receives final result if non-null boolean deferred; @@ -134,6 +134,7 @@ final class BroadcastRecord extends Binder { int manifestSkipCount; // number of manifest receivers skipped. int terminalCount; // number of receivers in terminal state. int deferredCount; // number of receivers in deferred state. + int beyondCount; // high-water number of receivers we've moved beyond. @Nullable BroadcastQueue queue; // the outbound queue handling this broadcast // Determines the privileges the app's process has in regard to background starts. @@ -219,6 +220,23 @@ final class BroadcastRecord extends Binder { } } + /** + * Return if the given delivery state is "beyond", which means that we've + * moved beyond this receiver, and future receivers are now unblocked. + */ + static boolean isDeliveryStateBeyond(@DeliveryState int deliveryState) { + switch (deliveryState) { + case DELIVERY_DELIVERED: + case DELIVERY_SKIPPED: + case DELIVERY_TIMEOUT: + case DELIVERY_FAILURE: + case DELIVERY_DEFERRED: + return true; + default: + return false; + } + } + ProcessRecord curApp; // hosting application of current receiver. ComponentName curComponent; // the receiver class that is currently running. ActivityInfo curReceiver; // the manifest receiver that is currently running. @@ -356,7 +374,7 @@ final class BroadcastRecord extends Binder { TimeUtils.formatDuration(terminalTime[i] - scheduledTime[i], pw); pw.print(' '); } - pw.print("("); pw.print(blockedUntilTerminalCount[i]); pw.print(") "); + pw.print("("); pw.print(blockedUntilBeyondCount[i]); pw.print(") "); pw.print("#"); pw.print(i); pw.print(": "); if (o instanceof BroadcastFilter) { pw.println(o); @@ -411,8 +429,7 @@ final class BroadcastRecord extends Binder { urgent = calculateUrgent(_intent, _options); deferUntilActive = calculateDeferUntilActive(_callingUid, _options, _resultTo, _serialized, urgent); - deferredUntilActive = new boolean[deferUntilActive ? delivery.length : 0]; - blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized); + blockedUntilBeyondCount = calculateBlockedUntilBeyondCount(receivers, _serialized); scheduledTime = new long[delivery.length]; terminalTime = new long[delivery.length]; resultToApp = _resultToApp; @@ -423,7 +440,7 @@ final class BroadcastRecord extends Binder { ordered = _serialized; sticky = _sticky; initialSticky = _initialSticky; - prioritized = isPrioritized(blockedUntilTerminalCount, _serialized); + prioritized = isPrioritized(blockedUntilBeyondCount, _serialized); userId = _userId; nextReceiver = 0; state = IDLE; @@ -467,8 +484,7 @@ final class BroadcastRecord extends Binder { delivery = from.delivery; deliveryReasons = from.deliveryReasons; deferUntilActive = from.deferUntilActive; - deferredUntilActive = from.deferredUntilActive; - blockedUntilTerminalCount = from.blockedUntilTerminalCount; + blockedUntilBeyondCount = from.blockedUntilBeyondCount; scheduledTime = from.scheduledTime; terminalTime = from.terminalTime; resultToApp = from.resultToApp; @@ -627,32 +643,72 @@ final class BroadcastRecord extends Binder { /** * Update the delivery state of the given {@link #receivers} index. * Automatically updates any time measurements related to state changes. + * + * @return if {@link #beyondCount} changed due to this state transition, + * indicating that other events may be unblocked. */ - void setDeliveryState(int index, @DeliveryState int deliveryState, + @CheckResult + boolean setDeliveryState(int index, @DeliveryState int newDeliveryState, @NonNull String reason) { - delivery[index] = deliveryState; - deliveryReasons[index] = reason; - if (deferUntilActive) deferredUntilActive[index] = false; - switch (deliveryState) { - case DELIVERY_DELIVERED: - case DELIVERY_SKIPPED: - case DELIVERY_TIMEOUT: - case DELIVERY_FAILURE: - terminalTime[index] = SystemClock.uptimeMillis(); + final int oldDeliveryState = delivery[index]; + if (isDeliveryStateTerminal(oldDeliveryState) + || newDeliveryState == oldDeliveryState) { + // We've already arrived in terminal or requested state, so leave + // any statistics and reasons intact from the first transition + return false; + } + + switch (oldDeliveryState) { + case DELIVERY_DEFERRED: + deferredCount--; break; + } + switch (newDeliveryState) { case DELIVERY_SCHEDULED: scheduledTime[index] = SystemClock.uptimeMillis(); break; case DELIVERY_DEFERRED: - if (deferUntilActive) deferredUntilActive[index] = true; + deferredCount++; + break; + case DELIVERY_DELIVERED: + case DELIVERY_SKIPPED: + case DELIVERY_TIMEOUT: + case DELIVERY_FAILURE: + terminalTime[index] = SystemClock.uptimeMillis(); + terminalCount++; break; } + + delivery[index] = newDeliveryState; + deliveryReasons[index] = reason; + + // If this state change might bring us to a new high-water mark, bring + // ourselves as high as we possibly can + final int oldBeyondCount = beyondCount; + if (index >= beyondCount) { + for (int i = beyondCount; i < delivery.length; i++) { + if (isDeliveryStateBeyond(getDeliveryState(i))) { + beyondCount = i + 1; + } else { + break; + } + } + } + return (beyondCount != oldBeyondCount); } @DeliveryState int getDeliveryState(int index) { return delivery[index]; } + /** + * @return if the given {@link #receivers} index should be considered + * blocked based on the current status of the overall broadcast. + */ + boolean isBlocked(int index) { + return (beyondCount < blockedUntilBeyondCount[index]); + } + boolean wasDeliveryAttempted(int index) { final int deliveryState = getDeliveryState(index); switch (deliveryState) { @@ -757,36 +813,36 @@ final class BroadcastRecord extends Binder { * has prioritized tranches of receivers. */ @VisibleForTesting - static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount, + static boolean isPrioritized(@NonNull int[] blockedUntilBeyondCount, boolean ordered) { - return !ordered && (blockedUntilTerminalCount.length > 0) - && (blockedUntilTerminalCount[0] != -1); + return !ordered && (blockedUntilBeyondCount.length > 0) + && (blockedUntilBeyondCount[0] != -1); } /** - * Calculate the {@link #terminalCount} that each receiver should be + * Calculate the {@link #beyondCount} that each receiver should be * considered blocked until. * <p> * For example, in an ordered broadcast, receiver {@code N} is blocked until - * receiver {@code N-1} reaches a terminal state. Similarly, in a - * prioritized broadcast, receiver {@code N} is blocked until all receivers - * of a higher priority reach a terminal state. + * receiver {@code N-1} reaches a terminal or deferred state. Similarly, in + * a prioritized broadcast, receiver {@code N} is blocked until all + * receivers of a higher priority reach a terminal or deferred state. * <p> - * When there are no terminal count constraints, the blocked value for each + * When there are no beyond count constraints, the blocked value for each * receiver is {@code -1}. */ @VisibleForTesting - static @NonNull int[] calculateBlockedUntilTerminalCount( + static @NonNull int[] calculateBlockedUntilBeyondCount( @NonNull List<Object> receivers, boolean ordered) { final int N = receivers.size(); - final int[] blockedUntilTerminalCount = new int[N]; + final int[] blockedUntilBeyondCount = new int[N]; int lastPriority = 0; int lastPriorityIndex = 0; for (int i = 0; i < N; i++) { if (ordered) { // When sending an ordered broadcast, we need to block this // receiver until all previous receivers have terminated - blockedUntilTerminalCount[i] = i; + blockedUntilBeyondCount[i] = i; } else { // When sending a prioritized broadcast, we only need to wait // for the previous tranche of receivers to be terminated @@ -794,18 +850,18 @@ final class BroadcastRecord extends Binder { if ((i == 0) || (thisPriority != lastPriority)) { lastPriority = thisPriority; lastPriorityIndex = i; - blockedUntilTerminalCount[i] = i; + blockedUntilBeyondCount[i] = i; } else { - blockedUntilTerminalCount[i] = lastPriorityIndex; + blockedUntilBeyondCount[i] = lastPriorityIndex; } } } // If the entire list is in the same priority tranche, mark as -1 to // indicate that none of them need to wait - if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) { - Arrays.fill(blockedUntilTerminalCount, -1); + if (N > 0 && blockedUntilBeyondCount[N - 1] == 0) { + Arrays.fill(blockedUntilBeyondCount, -1); } - return blockedUntilTerminalCount; + return blockedUntilBeyondCount; } static int getReceiverUid(@NonNull Object receiver) { diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index 4328efe0224a..8ad76cb668bf 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -18,6 +18,28 @@ package com.android.server.am; import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN; import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE; import static android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_COMPACTION; @@ -26,6 +48,7 @@ import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import android.annotation.IntDef; import android.app.ActivityManager; +import android.app.ActivityManagerInternal.OomAdjReason; import android.app.ActivityThread; import android.app.ApplicationExitInfo; import android.app.IApplicationThread; @@ -139,6 +162,26 @@ public final class CachedAppOptimizer { FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BINDER_TXNS; static final int UNFREEZE_REASON_FEATURE_FLAGS = FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_FEATURE_FLAGS; + static final int UNFREEZE_REASON_SHORT_FGS_TIMEOUT = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHORT_FGS_TIMEOUT; + static final int UNFREEZE_REASON_SYSTEM_INIT = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SYSTEM_INIT; + static final int UNFREEZE_REASON_BACKUP = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_BACKUP; + static final int UNFREEZE_REASON_SHELL = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_SHELL; + static final int UNFREEZE_REASON_REMOVE_TASK = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_REMOVE_TASK; + static final int UNFREEZE_REASON_UID_IDLE = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_UID_IDLE; + static final int UNFREEZE_REASON_STOP_SERVICE = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_STOP_SERVICE; + static final int UNFREEZE_REASON_EXECUTING_SERVICE = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_EXECUTING_SERVICE; + static final int UNFREEZE_REASON_RESTRICTION_CHANGE = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_RESTRICTION_CHANGE; + static final int UNFREEZE_REASON_COMPONENT_DISABLED = + FrameworkStatsLog.APP_FREEZE_CHANGED__UNFREEZE_REASON_V2__UFR_COMPONENT_DISABLED; @IntDef(prefix = {"UNFREEZE_REASON_"}, value = { UNFREEZE_REASON_NONE, @@ -160,6 +203,16 @@ public final class CachedAppOptimizer { UNFREEZE_REASON_FILE_LOCK_CHECK_FAILURE, UNFREEZE_REASON_BINDER_TXNS, UNFREEZE_REASON_FEATURE_FLAGS, + UNFREEZE_REASON_SHORT_FGS_TIMEOUT, + UNFREEZE_REASON_SYSTEM_INIT, + UNFREEZE_REASON_BACKUP, + UNFREEZE_REASON_SHELL, + UNFREEZE_REASON_REMOVE_TASK, + UNFREEZE_REASON_UID_IDLE, + UNFREEZE_REASON_STOP_SERVICE, + UNFREEZE_REASON_EXECUTING_SERVICE, + UNFREEZE_REASON_RESTRICTION_CHANGE, + UNFREEZE_REASON_COMPONENT_DISABLED, }) @Retention(RetentionPolicy.SOURCE) public @interface UnfreezeReason {} @@ -1273,8 +1326,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); @@ -1365,7 +1417,7 @@ public final class CachedAppOptimizer { * The caller of this function should still trigger updateOomAdj for AMS to unfreeze the app. * @param pid pid of the process to be unfrozen */ - void unfreezeProcess(int pid, @OomAdjuster.OomAdjReason int reason) { + void unfreezeProcess(int pid, @OomAdjReason int reason) { synchronized (mFreezerLock) { ProcessRecord app = mFrozenProcesses.get(pid); if (app == null) { @@ -1415,8 +1467,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()); @@ -1546,12 +1597,12 @@ public final class CachedAppOptimizer { public long mOrigAnonRss; public int mProcState; public int mOomAdj; - public @OomAdjuster.OomAdjReason int mOomAdjReason; + public @OomAdjReason int mOomAdjReason; SingleCompactionStats(long[] rss, CompactSource source, String processName, long deltaAnonRss, long zramConsumed, long anonMemFreed, long origAnonRss, long cpuTimeMillis, int procState, int oomAdj, - @OomAdjuster.OomAdjReason int oomAdjReason, int uid) { + @OomAdjReason int oomAdjReason, int uid) { mRssAfterCompaction = rss; mSourceType = source; mProcessName = processName; @@ -1945,6 +1996,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() { @@ -1975,7 +2035,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 { @@ -2086,8 +2148,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); } } @@ -2207,32 +2269,52 @@ public final class CachedAppOptimizer { } } - static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjuster.OomAdjReason int oomAdjReason) { + static int getUnfreezeReasonCodeFromOomAdjReason(@OomAdjReason int oomAdjReason) { switch (oomAdjReason) { - case OomAdjuster.OOM_ADJ_REASON_ACTIVITY: + case OOM_ADJ_REASON_ACTIVITY: return UNFREEZE_REASON_ACTIVITY; - case OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER: + case OOM_ADJ_REASON_FINISH_RECEIVER: return UNFREEZE_REASON_FINISH_RECEIVER; - case OomAdjuster.OOM_ADJ_REASON_START_RECEIVER: + case OOM_ADJ_REASON_START_RECEIVER: return UNFREEZE_REASON_START_RECEIVER; - case OomAdjuster.OOM_ADJ_REASON_BIND_SERVICE: + case OOM_ADJ_REASON_BIND_SERVICE: return UNFREEZE_REASON_BIND_SERVICE; - case OomAdjuster.OOM_ADJ_REASON_UNBIND_SERVICE: + case OOM_ADJ_REASON_UNBIND_SERVICE: return UNFREEZE_REASON_UNBIND_SERVICE; - case OomAdjuster.OOM_ADJ_REASON_START_SERVICE: + case OOM_ADJ_REASON_START_SERVICE: return UNFREEZE_REASON_START_SERVICE; - case OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER: + case OOM_ADJ_REASON_GET_PROVIDER: return UNFREEZE_REASON_GET_PROVIDER; - case OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER: + case OOM_ADJ_REASON_REMOVE_PROVIDER: return UNFREEZE_REASON_REMOVE_PROVIDER; - case OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY: + case OOM_ADJ_REASON_UI_VISIBILITY: return UNFREEZE_REASON_UI_VISIBILITY; - case OomAdjuster.OOM_ADJ_REASON_ALLOWLIST: + case OOM_ADJ_REASON_ALLOWLIST: return UNFREEZE_REASON_ALLOWLIST; - case OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN: + case OOM_ADJ_REASON_PROCESS_BEGIN: return UNFREEZE_REASON_PROCESS_BEGIN; - case OomAdjuster.OOM_ADJ_REASON_PROCESS_END: + case OOM_ADJ_REASON_PROCESS_END: return UNFREEZE_REASON_PROCESS_END; + case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT: + return UNFREEZE_REASON_SHORT_FGS_TIMEOUT; + case OOM_ADJ_REASON_SYSTEM_INIT: + return UNFREEZE_REASON_SYSTEM_INIT; + case OOM_ADJ_REASON_BACKUP: + return UNFREEZE_REASON_BACKUP; + case OOM_ADJ_REASON_SHELL: + return UNFREEZE_REASON_SHELL; + case OOM_ADJ_REASON_REMOVE_TASK: + return UNFREEZE_REASON_REMOVE_TASK; + case OOM_ADJ_REASON_UID_IDLE: + return UNFREEZE_REASON_UID_IDLE; + case OOM_ADJ_REASON_STOP_SERVICE: + return UNFREEZE_REASON_STOP_SERVICE; + case OOM_ADJ_REASON_EXECUTING_SERVICE: + return UNFREEZE_REASON_EXECUTING_SERVICE; + case OOM_ADJ_REASON_RESTRICTION_CHANGE: + return UNFREEZE_REASON_RESTRICTION_CHANGE; + case OOM_ADJ_REASON_COMPONENT_DISABLED: + return UNFREEZE_REASON_COMPONENT_DISABLED; default: return UNFREEZE_REASON_NONE; } diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java index a1fcd424f8c1..d8cb094caa65 100644 --- a/services/core/java/com/android/server/am/ContentProviderHelper.java +++ b/services/core/java/com/android/server/am/ContentProviderHelper.java @@ -16,6 +16,8 @@ package com.android.server.am; import static android.Manifest.permission.GET_ANY_PROVIDER_TYPE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER; import static android.content.ContentProvider.isAuthorityRedirectedForCloneProfile; import static android.os.Process.PROC_CHAR; import static android.os.Process.PROC_OUT_LONG; @@ -292,7 +294,7 @@ public class ContentProviderHelper { checkTime(startTime, "getContentProviderImpl: before updateOomAdj"); final int verifiedAdj = cpr.proc.mState.getVerifiedAdj(); boolean success = mService.updateOomAdjLocked(cpr.proc, - OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER); + OOM_ADJ_REASON_GET_PROVIDER); // XXX things have changed so updateOomAdjLocked doesn't actually tell us // if the process has been successfully adjusted. So to reduce races with // it, we will check whether the process still exists. Note that this doesn't @@ -757,7 +759,7 @@ public class ContentProviderHelper { // update the app's oom adj value and each provider's usage stats if (providersPublished) { - mService.updateOomAdjLocked(r, OomAdjuster.OOM_ADJ_REASON_GET_PROVIDER); + mService.updateOomAdjLocked(r, OOM_ADJ_REASON_GET_PROVIDER); for (int i = 0, size = providers.size(); i < size; i++) { ContentProviderHolder src = providers.get(i); if (src == null || src.info == null || src.provider == null) { @@ -835,8 +837,7 @@ public class ContentProviderHelper { ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId); if (localCpr.hasExternalProcessHandles()) { if (localCpr.removeExternalProcessHandleLocked(token)) { - mService.updateOomAdjLocked(localCpr.proc, - OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER); + mService.updateOomAdjLocked(localCpr.proc, OOM_ADJ_REASON_REMOVE_PROVIDER); } else { Slog.e(TAG, "Attempt to remove content provider " + localCpr + " with no external reference for token: " + token + "."); @@ -1506,8 +1507,7 @@ public class ContentProviderHelper { mService.stopAssociationLocked(conn.client.uid, conn.client.processName, cpr.uid, cpr.appInfo.longVersionCode, cpr.name, cpr.info.processName); if (updateOomAdj) { - mService.updateOomAdjLocked(conn.provider.proc, - OomAdjuster.OOM_ADJ_REASON_REMOVE_PROVIDER); + mService.updateOomAdjLocked(conn.provider.proc, OOM_ADJ_REASON_REMOVE_PROVIDER); } } } diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java index 9ff2cd0649d4..727d4df96c47 100644 --- a/services/core/java/com/android/server/am/DropboxRateLimiter.java +++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java @@ -22,7 +22,7 @@ import android.util.ArrayMap; import android.util.Slog; import com.android.internal.annotations.GuardedBy; -import com.android.internal.expresslog.Counter; +import com.android.modules.expresslog.Counter; /** Rate limiter for adding errors into dropbox. */ public class DropboxRateLimiter { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index a98571b68067..365dcd9bd785 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -41,6 +41,29 @@ import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; import static android.app.ActivityManager.PROCESS_STATE_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ALLOWLIST; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BACKUP; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_BIND_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_COMPONENT_DISABLED; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_EXECUTING_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_GET_PROVIDER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_BEGIN; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_PROVIDER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_REMOVE_TASK; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHELL; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_STOP_SERVICE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_SYSTEM_INIT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UID_IDLE; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UNBIND_SERVICE; import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; @@ -101,9 +124,9 @@ import static com.android.server.am.ProcessList.UNKNOWN_ADJ; import static com.android.server.am.ProcessList.VISIBLE_APP_ADJ; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; -import android.annotation.IntDef; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityManagerInternal.OomAdjReason; import android.app.ActivityThread; import android.app.AppProtoEnums; import android.app.ApplicationExitInfo; @@ -141,8 +164,6 @@ import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.WindowProcessController; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -154,32 +175,6 @@ import java.util.List; public class OomAdjuster { static final String TAG = "OomAdjuster"; - static final int OOM_ADJ_REASON_NONE = 0; - static final int OOM_ADJ_REASON_ACTIVITY = 1; - static final int OOM_ADJ_REASON_FINISH_RECEIVER = 2; - static final int OOM_ADJ_REASON_START_RECEIVER = 3; - static final int OOM_ADJ_REASON_BIND_SERVICE = 4; - static final int OOM_ADJ_REASON_UNBIND_SERVICE = 5; - static final int OOM_ADJ_REASON_START_SERVICE = 6; - static final int OOM_ADJ_REASON_GET_PROVIDER = 7; - static final int OOM_ADJ_REASON_REMOVE_PROVIDER = 8; - static final int OOM_ADJ_REASON_UI_VISIBILITY = 9; - static final int OOM_ADJ_REASON_ALLOWLIST = 10; - static final int OOM_ADJ_REASON_PROCESS_BEGIN = 11; - static final int OOM_ADJ_REASON_PROCESS_END = 12; - static final int OOM_ADJ_REASON_SHORT_FGS_TIMEOUT = 13; - - @IntDef(prefix = {"OOM_ADJ_REASON_"}, - value = {OOM_ADJ_REASON_NONE, OOM_ADJ_REASON_ACTIVITY, OOM_ADJ_REASON_FINISH_RECEIVER, - OOM_ADJ_REASON_START_RECEIVER, OOM_ADJ_REASON_BIND_SERVICE, - OOM_ADJ_REASON_UNBIND_SERVICE, OOM_ADJ_REASON_START_SERVICE, - OOM_ADJ_REASON_GET_PROVIDER, OOM_ADJ_REASON_REMOVE_PROVIDER, - OOM_ADJ_REASON_UI_VISIBILITY, OOM_ADJ_REASON_ALLOWLIST, - OOM_ADJ_REASON_PROCESS_BEGIN, OOM_ADJ_REASON_PROCESS_END, - OOM_ADJ_REASON_SHORT_FGS_TIMEOUT}) - @Retention(RetentionPolicy.SOURCE) - public @interface OomAdjReason {} - public static final int oomAdjReasonToProto(@OomAdjReason int oomReason) { switch (oomReason) { case OOM_ADJ_REASON_NONE: @@ -210,6 +205,24 @@ public class OomAdjuster { return AppProtoEnums.OOM_ADJ_REASON_PROCESS_END; case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT: return AppProtoEnums.OOM_ADJ_REASON_SHORT_FGS_TIMEOUT; + case OOM_ADJ_REASON_SYSTEM_INIT: + return AppProtoEnums.OOM_ADJ_REASON_SYSTEM_INIT; + case OOM_ADJ_REASON_BACKUP: + return AppProtoEnums.OOM_ADJ_REASON_BACKUP; + case OOM_ADJ_REASON_SHELL: + return AppProtoEnums.OOM_ADJ_REASON_SHELL; + case OOM_ADJ_REASON_REMOVE_TASK: + return AppProtoEnums.OOM_ADJ_REASON_REMOVE_TASK; + case OOM_ADJ_REASON_UID_IDLE: + return AppProtoEnums.OOM_ADJ_REASON_UID_IDLE; + case OOM_ADJ_REASON_STOP_SERVICE: + return AppProtoEnums.OOM_ADJ_REASON_STOP_SERVICE; + case OOM_ADJ_REASON_EXECUTING_SERVICE: + return AppProtoEnums.OOM_ADJ_REASON_EXECUTING_SERVICE; + case OOM_ADJ_REASON_RESTRICTION_CHANGE: + return AppProtoEnums.OOM_ADJ_REASON_RESTRICTION_CHANGE; + case OOM_ADJ_REASON_COMPONENT_DISABLED: + return AppProtoEnums.OOM_ADJ_REASON_COMPONENT_DISABLED; default: return AppProtoEnums.OOM_ADJ_REASON_UNKNOWN_TO_PROTO; } @@ -246,6 +259,24 @@ public class OomAdjuster { return OOM_ADJ_REASON_METHOD + "_processEnd"; case OOM_ADJ_REASON_SHORT_FGS_TIMEOUT: return OOM_ADJ_REASON_METHOD + "_shortFgs"; + case OOM_ADJ_REASON_SYSTEM_INIT: + return OOM_ADJ_REASON_METHOD + "_systemInit"; + case OOM_ADJ_REASON_BACKUP: + return OOM_ADJ_REASON_METHOD + "_backup"; + case OOM_ADJ_REASON_SHELL: + return OOM_ADJ_REASON_METHOD + "_shell"; + case OOM_ADJ_REASON_REMOVE_TASK: + return OOM_ADJ_REASON_METHOD + "_removeTask"; + case OOM_ADJ_REASON_UID_IDLE: + return OOM_ADJ_REASON_METHOD + "_uidIdle"; + case OOM_ADJ_REASON_STOP_SERVICE: + return OOM_ADJ_REASON_METHOD + "_stopService"; + case OOM_ADJ_REASON_EXECUTING_SERVICE: + return OOM_ADJ_REASON_METHOD + "_executingService"; + case OOM_ADJ_REASON_RESTRICTION_CHANGE: + return OOM_ADJ_REASON_METHOD + "_restrictionChange"; + case OOM_ADJ_REASON_COMPONENT_DISABLED: + return OOM_ADJ_REASON_METHOD + "_componentDisabled"; default: return "_unknown"; } @@ -874,8 +905,7 @@ public class OomAdjuster { } @GuardedBy("mService") - private void performUpdateOomAdjPendingTargetsLocked( - @OomAdjuster.OomAdjReason int oomAdjReason) { + private void performUpdateOomAdjPendingTargetsLocked(@OomAdjReason int oomAdjReason) { final ProcessRecord topApp = mService.getTopApp(); Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, oomAdjReasonToString(oomAdjReason)); @@ -3453,7 +3483,7 @@ public class OomAdjuster { } @GuardedBy("mService") - void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) { + void unfreezeTemporarily(ProcessRecord app, @OomAdjReason int reason) { if (!mCachedAppOptimizer.useFreezer()) { return; } diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java index 24cc5337b86f..f2331072ce51 100644 --- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java +++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java @@ -16,6 +16,8 @@ package com.android.server.am; +import android.app.ActivityManagerInternal.OomAdjReason; + import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -51,7 +53,7 @@ final class ProcessCachedOptimizerRecord { /** * Last oom adjust change reason for this app. */ - @GuardedBy("mProcLock") private @OomAdjuster.OomAdjReason int mLastOomAdjChangeReason; + @GuardedBy("mProcLock") private @OomAdjReason int mLastOomAdjChangeReason; /** * The most recent compaction action performed for this app. @@ -139,12 +141,12 @@ final class ProcessCachedOptimizerRecord { } @GuardedBy("mProcLock") - void setLastOomAdjChangeReason(@OomAdjuster.OomAdjReason int reason) { + void setLastOomAdjChangeReason(@OomAdjReason int reason) { mLastOomAdjChangeReason = reason; } @GuardedBy("mProcLock") - @OomAdjuster.OomAdjReason + @OomAdjReason int getLastOomAdjChangeReason() { return mLastOomAdjChangeReason; } diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 70a696c72a9d..ca41f429a0de 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -51,7 +51,7 @@ import android.util.SparseBooleanArray; import com.android.internal.annotations.CompositeRWLock; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.expresslog.Counter; +import com.android.modules.expresslog.Counter; import com.android.internal.os.ProcessCpuTracker; import com.android.internal.os.TimeoutRecord; import com.android.internal.os.anr.AnrLatencyTracker; diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index b1322ef510d5..312f98ad6e09 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -19,6 +19,8 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_PROCESS_END; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_RESTRICTION_CHANGE; import static android.app.ActivityThread.PROC_START_SEQ_IDENT; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO; import static android.net.NetworkPolicyManager.isProcStateAllowedWhileIdleOrPowerSaveMode; @@ -2875,7 +2877,7 @@ public final class ProcessList { reasonCode, subReason, reason, !doFreeze /* async */); } killAppZygotesLocked(packageName, appId, userId, false /* force */); - mService.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_PROCESS_END); + mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END); if (doFreeze) { freezePackageCgroup(packageUID, false); } @@ -5140,7 +5142,7 @@ public final class ProcessList { } }); /* Will be a no-op if nothing pending */ - mService.updateOomAdjPendingTargetsLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_RESTRICTION_CHANGE); } } @@ -5203,6 +5205,17 @@ public final class ProcessList { } /** + * Called by ActivityManagerService when a recoverable native crash occurs. + */ + @GuardedBy("mService") + void noteAppRecoverableCrash(final ProcessRecord app) { + if (DEBUG_PROCESSES) { + Slog.i(TAG, "note: " + app + " has a recoverable native crash"); + } + mAppExitInfoTracker.scheduleNoteAppRecoverableCrash(app); + } + + /** * Called by ActivityManagerService when it decides to kill an application process. */ @GuardedBy("mService") diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index afae623cd217..ffb40ee959a4 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; + import static com.android.internal.util.Preconditions.checkArgument; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; @@ -577,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; } @@ -1450,7 +1454,7 @@ class ProcessRecord implements WindowProcessListener { } mService.updateLruProcessLocked(this, activityChange, null /* client */); if (updateOomAdj) { - mService.updateOomAdjLocked(this, OomAdjuster.OOM_ADJ_REASON_ACTIVITY); + mService.updateOomAdjLocked(this, OOM_ADJ_REASON_ACTIVITY); } } } diff --git a/services/core/java/com/android/server/am/ProcessStateRecord.java b/services/core/java/com/android/server/am/ProcessStateRecord.java index 71d5d39525b4..ab71acd5f21d 100644 --- a/services/core/java/com/android/server/am/ProcessStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessStateRecord.java @@ -18,6 +18,7 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_UI_VISIBILITY; import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ; import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_ACTIVITY; @@ -613,7 +614,7 @@ final class ProcessStateRecord { void forceProcessStateUpTo(int newState) { if (mRepProcState > newState) { synchronized (mProcLock) { - mRepProcState = newState; + setReportedProcState(newState); setCurProcState(newState); setCurRawProcState(newState); } @@ -766,7 +767,7 @@ final class ProcessStateRecord { Slog.i(TAG, "Setting runningRemoteAnimation=" + runningRemoteAnimation + " for pid=" + mApp.getPid()); } - mService.updateOomAdjLocked(mApp, OomAdjuster.OOM_ADJ_REASON_UI_VISIBILITY); + mService.updateOomAdjLocked(mApp, OOM_ADJ_REASON_UI_VISIBILITY); } @GuardedBy({"mService", "mProcLock"}) diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java index 22e2c9fd889b..8c227f5488d3 100644 --- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java +++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java @@ -100,7 +100,6 @@ public class SettingsToPropertiesMapper { DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT, DeviceConfig.NAMESPACE_SURFACE_FLINGER_NATIVE_BOOT, DeviceConfig.NAMESPACE_SWCODEC_NATIVE, - DeviceConfig.NAMESPACE_TETHERING, DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE, DeviceConfig.NAMESPACE_VENDOR_SYSTEM_NATIVE_BOOT, DeviceConfig.NAMESPACE_VIRTUALIZATION_FRAMEWORK_NATIVE, diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 81ba4b813de1..a110169ac8c2 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -34,6 +34,7 @@ import static android.app.AppOpsManager.MODE_ERRORED; import static android.app.AppOpsManager.MODE_FOREGROUND; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.AppOpsManager.OP_CAMERA; +import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED; import static android.app.AppOpsManager.OP_FLAGS_ALL; import static android.app.AppOpsManager.OP_FLAG_SELF; import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; @@ -42,6 +43,7 @@ import static android.app.AppOpsManager.OP_PLAY_AUDIO; import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO_HOTWORD; +import static android.app.AppOpsManager.OP_RECORD_AUDIO_SANDBOXED; import static android.app.AppOpsManager.OP_VIBRATE; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_FAILED; import static android.app.AppOpsManager.OnOpStartedListener.START_TYPE_STARTED; @@ -3027,17 +3029,29 @@ public class AppOpsService extends IAppOpsService.Stub { packageName); } - // As a special case for OP_RECORD_AUDIO_HOTWORD, which we use only for attribution - // purposes and not as a check, also make sure that the caller is allowed to access - // the data gated by OP_RECORD_AUDIO. + // As a special case for OP_RECORD_AUDIO_HOTWORD, OP_RECEIVE_AMBIENT_TRIGGER_AUDIO and + // OP_RECORD_AUDIO_SANDBOXED which we use only for attribution purposes and not as a check, + // also make sure that the caller is allowed to access the data gated by OP_RECORD_AUDIO. // // TODO: Revert this change before Android 12. - if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO) { - int result = checkOperation(OP_RECORD_AUDIO, uid, packageName); + int result = MODE_DEFAULT; + if (code == OP_RECORD_AUDIO_HOTWORD || code == OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + || code == OP_RECORD_AUDIO_SANDBOXED) { + result = checkOperation(OP_RECORD_AUDIO, uid, packageName); + // Check result if (result != AppOpsManager.MODE_ALLOWED) { return new SyncNotedAppOp(result, code, attributionTag, packageName); } } + // As a special case for OP_CAMERA_SANDBOXED. + if (code == OP_CAMERA_SANDBOXED) { + result = checkOperation(OP_CAMERA, uid, packageName); + // Check result + if (result != AppOpsManager.MODE_ALLOWED) { + return new SyncNotedAppOp(result, code, attributionTag, packageName); + } + } + return startOperationUnchecked(clientId, code, uid, packageName, attributionTag, Process.INVALID_UID, null, null, OP_FLAG_SELF, startIfModeDefault, shouldCollectAsyncNotedOp, message, shouldCollectMessage, attributionFlags, diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index 7c8e6df4acdc..5127d26e6e73 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -19,6 +19,8 @@ package com.android.server.biometrics; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR; +import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_VENDOR_BASE; import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT; import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE; @@ -519,6 +521,9 @@ public final class AuthSession implements IBinder.DeathRecipient { try { mStatusBarService.onBiometricHelp(sensorIdToModality(sensorId), message); + final int aAcquiredInfo = acquiredInfo == FINGERPRINT_ACQUIRED_VENDOR + ? (vendorCode + FINGERPRINT_ACQUIRED_VENDOR_BASE) : acquiredInfo; + mClientReceiver.onAcquired(aAcquiredInfo, message); } catch (RemoteException e) { Slog.e(TAG, "Remote exception", e); } 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/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 128ef0b2a802..6c26e2b0ce99 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -413,6 +413,11 @@ public class FingerprintService extends SystemService { Slog.e(TAG, "Remote exception in onAuthenticationAcquired()", e); } } + + @Override + public void onAuthenticationHelp(int acquireInfo, CharSequence helpString) { + onAuthenticationAcquired(acquireInfo); + } }; return biometricPrompt.authenticateForOperation( diff --git a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java index 2ac283370886..c039a836843c 100644 --- a/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java +++ b/services/core/java/com/android/server/graphics/fonts/FontManagerShellCommand.java @@ -334,8 +334,8 @@ public class FontManagerShellCommand extends ShellCommand { } private int installCert(ShellCommand shell) throws SystemFontException { - if (!(Build.IS_USERDEBUG || Build.IS_ENG)) { - throw new SecurityException("Only userdebug/eng device can add debug certificate"); + if (!Build.IS_DEBUGGABLE) { + throw new SecurityException("Only debuggable device can add debug certificate"); } if (Binder.getCallingUid() != Process.ROOT_UID) { throw new SecurityException("Only root can add debug certificate"); diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java index 43e346a5bfa3..2d4066144a7f 100644 --- a/services/core/java/com/android/server/locales/LocaleManagerService.java +++ b/services/core/java/com/android/server/locales/LocaleManagerService.java @@ -323,7 +323,7 @@ public class LocaleManagerService extends SystemService { */ void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId, LocaleList locales) { - String installingPackageName = getInstallingPackageName(appPackageName); + String installingPackageName = getInstallingPackageName(appPackageName, userId); if (installingPackageName != null) { Intent intent = createBaseIntent(Intent.ACTION_APPLICATION_LOCALE_CHANGED, appPackageName, locales); @@ -464,7 +464,7 @@ public class LocaleManagerService extends SystemService { * Checks if the calling app is the installer of the app whose locale changed. */ private boolean isCallerInstaller(String appPackageName, int userId) { - String installingPackageName = getInstallingPackageName(appPackageName); + String installingPackageName = getInstallingPackageName(appPackageName, userId); if (installingPackageName != null) { // Get the uid of installer-on-record to compare with the calling uid. int installerUid = getPackageUid(installingPackageName, userId); @@ -513,10 +513,11 @@ public class LocaleManagerService extends SystemService { } @Nullable - String getInstallingPackageName(String packageName) { + String getInstallingPackageName(String packageName, int userId) { try { - return mContext.getPackageManager() - .getInstallSourceInfo(packageName).getInstallingPackageName(); + return mContext.createContextAsUser(UserHandle.of(userId), /* flags= */ + 0).getPackageManager().getInstallSourceInfo( + packageName).getInstallingPackageName(); } catch (PackageManager.NameNotFoundException e) { Slog.w(TAG, "Package not found " + packageName); } diff --git a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java index 215c653f1be7..373d3553e0eb 100644 --- a/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java +++ b/services/core/java/com/android/server/locales/SystemAppUpdateTracker.java @@ -152,9 +152,10 @@ public class SystemAppUpdateTracker { void onPackageUpdateFinished(String packageName, int uid) { try { if ((!mUpdatedApps.contains(packageName)) && isUpdatedSystemApp(packageName)) { + int userId = UserHandle.getUserId(uid); // If a system app is updated, verify that it has an installer-on-record. String installingPackageName = mLocaleManagerService.getInstallingPackageName( - packageName); + packageName, userId); if (installingPackageName == null) { // We want to broadcast the locales info to the installer. // If this app does not have an installer then do nothing. @@ -162,7 +163,6 @@ public class SystemAppUpdateTracker { } try { - int userId = UserHandle.getUserId(uid); // Fetch the app-specific locales. // If non-empty then send the info to the installer. LocaleList appLocales = mLocaleManagerService.getApplicationLocales( diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index 653b71828c5b..5f783742bd26 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -519,17 +519,24 @@ public class ContextHubService extends IContextHubService.Stub { BroadcastReceiver btReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction()) - || BluetoothAdapter.ACTION_BLE_STATE_CHANGED.equals( - intent.getAction())) { + if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) { sendBtSettingUpdate(/* forceUpdate= */ false); } } }; IntentFilter filter = new IntentFilter(); filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); - filter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED); mContext.registerReceiver(btReceiver, filter); + + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.BLE_SCAN_ALWAYS_AVAILABLE), + /* notifyForDescendants= */ false, + new ContentObserver(/* handler= */ null) { + @Override + public void onChange(boolean selfChange) { + sendBtSettingUpdate(/* forceUpdate= */ false); + } + }, UserHandle.USER_ALL); } /** diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java index 273afcc9f769..dff02bf711cd 100644 --- a/services/core/java/com/android/server/notification/GroupHelper.java +++ b/services/core/java/com/android/server/notification/GroupHelper.java @@ -15,44 +15,53 @@ */ package com.android.server.notification; +import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY; +import static android.app.Notification.FLAG_AUTO_CANCEL; +import static android.app.Notification.FLAG_GROUP_SUMMARY; +import static android.app.Notification.FLAG_LOCAL_ONLY; +import static android.app.Notification.FLAG_NO_CLEAR; +import static android.app.Notification.FLAG_ONGOING_EVENT; + +import android.annotation.NonNull; import android.service.notification.StatusBarNotification; import android.util.ArrayMap; -import android.util.ArraySet; -import android.util.Log; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; /** * NotificationManagerService helper for auto-grouping notifications. */ public class GroupHelper { private static final String TAG = "GroupHelper"; - private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); protected static final String AUTOGROUP_KEY = "ranker_group"; + // Flags that all autogroup summaries have + protected static final int BASE_FLAGS = + FLAG_AUTOGROUP_SUMMARY | FLAG_GROUP_SUMMARY | FLAG_LOCAL_ONLY; + // Flag that autogroup summaries inherits if all children have the flag + private static final int ALL_CHILDREN_FLAG = FLAG_AUTO_CANCEL; + // Flags that autogroup summaries inherits if any child has them + private static final int ANY_CHILDREN_FLAGS = FLAG_ONGOING_EVENT | FLAG_NO_CLEAR; + private final Callback mCallback; private final int mAutoGroupAtCount; - // count the number of ongoing notifications per group - // userId|packageName -> (set of ongoing notifications that aren't in an app group) - final ArrayMap<String, ArraySet<String>> - mOngoingGroupCount = new ArrayMap<>(); - - // Map of user : <Map of package : notification keys>. Only contains notifications that are not - // grouped by the app (aka no group or sort key). - Map<Integer, Map<String, LinkedHashSet<String>>> mUngroupedNotifications = new HashMap<>(); + // Only contains notifications that are not explicitly grouped by the app (aka no group or + // sort key). + // userId|packageName -> (keys of notifications that aren't in an explicit app group -> flags) + @GuardedBy("mUngroupedNotifications") + private final ArrayMap<String, ArrayMap<String, Integer>> mUngroupedNotifications + = new ArrayMap<>(); public GroupHelper(int autoGroupAtCount, Callback callback) { mAutoGroupAtCount = autoGroupAtCount; - mCallback = callback; + mCallback = callback; } private String generatePackageKey(int userId, String pkg) { @@ -60,69 +69,30 @@ public class GroupHelper { } @VisibleForTesting - protected int getOngoingGroupCount(int userId, String pkg) { - String key = generatePackageKey(userId, pkg); - return mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0)).size(); - } - - private void updateOngoingGroupCount(StatusBarNotification sbn, boolean add) { - if (sbn.getNotification().isGroupSummary()) { - return; - } - String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName()); - ArraySet<String> notifications = mOngoingGroupCount.getOrDefault(key, new ArraySet<>(0)); - if (add) { - notifications.add(sbn.getKey()); - mOngoingGroupCount.put(key, notifications); - } else { - notifications.remove(sbn.getKey()); - // we don't need to put it back if it is default + @GuardedBy("mUngroupedNotifications") + protected int getAutogroupSummaryFlags(@NonNull final ArrayMap<String, Integer> children) { + boolean allChildrenHasFlag = children.size() > 0; + int anyChildFlagSet = 0; + for (int i = 0; i < children.size(); i++) { + if (!hasAnyFlag(children.valueAt(i), ALL_CHILDREN_FLAG)) { + allChildrenHasFlag = false; + } + if (hasAnyFlag(children.valueAt(i), ANY_CHILDREN_FLAGS)) { + anyChildFlagSet |= (children.valueAt(i) & ANY_CHILDREN_FLAGS); + } } - - boolean needsOngoingFlag = notifications.size() > 0; - mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), needsOngoingFlag); + return BASE_FLAGS | (allChildrenHasFlag ? ALL_CHILDREN_FLAG : 0) | anyChildFlagSet; } - public void onNotificationUpdated(StatusBarNotification childSbn) { - updateOngoingGroupCount(childSbn, childSbn.isOngoing() && !childSbn.isAppGroup()); + private boolean hasAnyFlag(int flags, int mask) { + return (flags & mask) != 0; } public void onNotificationPosted(StatusBarNotification sbn, boolean autogroupSummaryExists) { try { - updateOngoingGroupCount(sbn, sbn.isOngoing() && !sbn.isAppGroup()); - - List<String> notificationsToGroup = new ArrayList<>(); if (!sbn.isAppGroup()) { - // Not grouped by the app, add to the list of notifications for the app; - // send grouping update if app exceeds the autogrouping limit. - synchronized (mUngroupedNotifications) { - Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser - = mUngroupedNotifications.get(sbn.getUserId()); - if (ungroupedNotificationsByUser == null) { - ungroupedNotificationsByUser = new HashMap<>(); - } - mUngroupedNotifications.put(sbn.getUserId(), ungroupedNotificationsByUser); - LinkedHashSet<String> notificationsForPackage - = ungroupedNotificationsByUser.get(sbn.getPackageName()); - if (notificationsForPackage == null) { - notificationsForPackage = new LinkedHashSet<>(); - } - - notificationsForPackage.add(sbn.getKey()); - ungroupedNotificationsByUser.put(sbn.getPackageName(), notificationsForPackage); - - if (notificationsForPackage.size() >= mAutoGroupAtCount - || autogroupSummaryExists) { - notificationsToGroup.addAll(notificationsForPackage); - } - } - if (notificationsToGroup.size() > 0) { - adjustAutogroupingSummary(sbn.getUserId(), sbn.getPackageName(), - notificationsToGroup.get(0), true); - adjustNotificationBundling(notificationsToGroup, true); - } + maybeGroup(sbn, autogroupSummaryExists); } else { - // Grouped, but not by us. Send updates to un-autogroup, if we grouped it. maybeUngroup(sbn, false, sbn.getUserId()); } @@ -133,7 +103,6 @@ public class GroupHelper { public void onNotificationRemoved(StatusBarNotification sbn) { try { - updateOngoingGroupCount(sbn, false); maybeUngroup(sbn, true, sbn.getUserId()); } catch (Exception e) { Slog.e(TAG, "Error processing canceled notification", e); @@ -141,70 +110,114 @@ public class GroupHelper { } /** - * Un-autogroups notifications that are now grouped by the app. + * A non-app grouped notification has been added or updated + * Evaluate if: + * (a) an existing autogroup summary needs updated flags + * (b) a new autogroup summary needs to be added with correct flags + * (c) other non-app grouped children need to be moved to the autogroup + * + * And stores the list of upgrouped notifications & their flags + */ + private void maybeGroup(StatusBarNotification sbn, boolean autogroupSummaryExists) { + int flags = 0; + List<String> notificationsToGroup = new ArrayList<>(); + synchronized (mUngroupedNotifications) { + String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName()); + final ArrayMap<String, Integer> children = + mUngroupedNotifications.getOrDefault(key, new ArrayMap<>()); + + children.put(sbn.getKey(), sbn.getNotification().flags); + mUngroupedNotifications.put(key, children); + + if (children.size() >= mAutoGroupAtCount || autogroupSummaryExists) { + flags = getAutogroupSummaryFlags(children); + notificationsToGroup.addAll(children.keySet()); + } + } + if (notificationsToGroup.size() > 0) { + if (autogroupSummaryExists) { + mCallback.updateAutogroupSummary(sbn.getUserId(), sbn.getPackageName(), flags); + } else { + mCallback.addAutoGroupSummary( + sbn.getUserId(), sbn.getPackageName(), sbn.getKey(), flags); + } + for (String key : notificationsToGroup) { + mCallback.addAutoGroup(key); + } + } + } + + /** + * A notification was added that's app grouped, or a notification was removed. + * Evaluate whether: + * (a) an existing autogroup summary needs updated flags + * (b) if we need to remove our autogroup overlay for this notification + * (c) we need to remove the autogroup summary + * + * And updates the internal state of un-app-grouped notifications and their flags */ private void maybeUngroup(StatusBarNotification sbn, boolean notificationGone, int userId) { - List<String> notificationsToUnAutogroup = new ArrayList<>(); boolean removeSummary = false; + int summaryFlags = 0; + boolean updateSummaryFlags = false; + boolean removeAutogroupOverlay = false; synchronized (mUngroupedNotifications) { - Map<String, LinkedHashSet<String>> ungroupedNotificationsByUser - = mUngroupedNotifications.get(sbn.getUserId()); - if (ungroupedNotificationsByUser == null || ungroupedNotificationsByUser.size() == 0) { - return; - } - LinkedHashSet<String> notificationsForPackage - = ungroupedNotificationsByUser.get(sbn.getPackageName()); - if (notificationsForPackage == null || notificationsForPackage.size() == 0) { + String key = generatePackageKey(sbn.getUserId(), sbn.getPackageName()); + final ArrayMap<String, Integer> children = + mUngroupedNotifications.getOrDefault(key, new ArrayMap<>()); + if (children.size() == 0) { return; } - if (notificationsForPackage.remove(sbn.getKey())) { - if (!notificationGone) { - // Add the current notification to the ungrouping list if it still exists. - notificationsToUnAutogroup.add(sbn.getKey()); + + // if this notif was autogrouped and now isn't + if (children.containsKey(sbn.getKey())) { + // if this notification was contributing flags that aren't covered by other + // children to the summary, reevaluate flags for the summary + int flags = children.remove(sbn.getKey()); + // this + if (hasAnyFlag(flags, ANY_CHILDREN_FLAGS)) { + updateSummaryFlags = true; + summaryFlags = getAutogroupSummaryFlags(children); + } + // if this notification still exists and has an autogroup overlay, but is now + // grouped by the app, clear the overlay + if (!notificationGone && sbn.getOverrideGroupKey() != null) { + removeAutogroupOverlay = true; + } + + // If there are no more children left to autogroup, remove the summary + if (children.size() == 0) { + removeSummary = true; } - } - // If the status change of this notification has brought the number of loose - // notifications to zero, remove the summary and un-autogroup. - if (notificationsForPackage.size() == 0) { - ungroupedNotificationsByUser.remove(sbn.getPackageName()); - removeSummary = true; } } if (removeSummary) { - adjustAutogroupingSummary(userId, sbn.getPackageName(), null, false); - } - if (notificationsToUnAutogroup.size() > 0) { - adjustNotificationBundling(notificationsToUnAutogroup, false); - } - } - - private void adjustAutogroupingSummary(int userId, String packageName, String triggeringKey, - boolean summaryNeeded) { - if (summaryNeeded) { - mCallback.addAutoGroupSummary(userId, packageName, triggeringKey, - getOngoingGroupCount(userId, packageName) > 0); + mCallback.removeAutoGroupSummary(userId, sbn.getPackageName()); } else { - mCallback.removeAutoGroupSummary(userId, packageName); + if (updateSummaryFlags) { + mCallback.updateAutogroupSummary(userId, sbn.getPackageName(), summaryFlags); + } + } + if (removeAutogroupOverlay) { + mCallback.removeAutoGroup(sbn.getKey()); } } - private void adjustNotificationBundling(List<String> keys, boolean group) { - for (String key : keys) { - if (DEBUG) Log.i(TAG, "Sending grouping adjustment for: " + key + " group? " + group); - if (group) { - mCallback.addAutoGroup(key); - } else { - mCallback.removeAutoGroup(key); - } + @VisibleForTesting + int getNotGroupedByAppCount(int userId, String pkg) { + synchronized (mUngroupedNotifications) { + String key = generatePackageKey(userId, pkg); + final ArrayMap<String, Integer> children = + mUngroupedNotifications.getOrDefault(key, new ArrayMap<>()); + return children.size(); } } protected interface Callback { void addAutoGroup(String key); void removeAutoGroup(String key); - void addAutoGroupSummary(int userId, String pkg, String triggeringKey, - boolean needsOngoingFlag); + void addAutoGroupSummary(int userId, String pkg, String triggeringKey, int flags); void removeAutoGroupSummary(int user, String pkg); - void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag); + void updateAutogroupSummary(int userId, String pkg, int flags); } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5d81dda5f5eb..1301cd476c26 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -319,6 +319,7 @@ import com.android.server.pm.PackageManagerService; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.policy.PermissionPolicyInternal; +import com.android.server.powerstats.StatsPullAtomCallbackImpl; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.utils.Slogf; @@ -902,11 +903,11 @@ public class NotificationManagerService extends SystemService { * has the same flag. It will delete the flag otherwise * @param userId user id of the autogroup summary * @param pkg package of the autogroup summary - * @param needsOngoingFlag true if the group has at least one ongoing notification + * @param flags the new flags for this summary * @param isAppForeground true if the app is currently in the foreground. */ @GuardedBy("mNotificationLock") - protected void updateAutobundledSummaryFlags(int userId, String pkg, boolean needsOngoingFlag, + protected void updateAutobundledSummaryFlags(int userId, String pkg, int flags, boolean isAppForeground) { ArrayMap<String, String> summaries = mAutobundledSummaries.get(userId); if (summaries == null) { @@ -921,13 +922,8 @@ public class NotificationManagerService extends SystemService { return; } int oldFlags = summary.getSbn().getNotification().flags; - if (needsOngoingFlag) { - summary.getSbn().getNotification().flags |= FLAG_ONGOING_EVENT; - } else { - summary.getSbn().getNotification().flags &= ~FLAG_ONGOING_EVENT; - } - - if (summary.getSbn().getNotification().flags != oldFlags) { + if (oldFlags != flags) { + summary.getSbn().getNotification().flags = flags; mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground, SystemClock.elapsedRealtime())); } @@ -2684,9 +2680,14 @@ public class NotificationManagerService extends SystemService { @Override public void addAutoGroupSummary(int userId, String pkg, String triggeringKey, - boolean needsOngoingFlag) { - NotificationManagerService.this.addAutoGroupSummary( - userId, pkg, triggeringKey, needsOngoingFlag); + int flags) { + NotificationRecord r = createAutoGroupSummary(userId, pkg, triggeringKey, flags); + if (r != null) { + final boolean isAppForeground = + mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; + mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground, + SystemClock.elapsedRealtime())); + } } @Override @@ -2697,11 +2698,11 @@ public class NotificationManagerService extends SystemService { } @Override - public void updateAutogroupSummary(int userId, String pkg, boolean needsOngoingFlag) { + public void updateAutogroupSummary(int userId, String pkg, int flags) { boolean isAppForeground = pkg != null && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; synchronized (mNotificationLock) { - updateAutobundledSummaryFlags(userId, pkg, needsOngoingFlag, isAppForeground); + updateAutobundledSummaryFlags(userId, pkg, flags, isAppForeground); } } }); @@ -5961,19 +5962,6 @@ public class NotificationManagerService extends SystemService { r.addAdjustment(adjustment); } - @VisibleForTesting - void addAutoGroupSummary(int userId, String pkg, String triggeringKey, - boolean needsOngoingFlag) { - NotificationRecord r = createAutoGroupSummary( - userId, pkg, triggeringKey, needsOngoingFlag); - if (r != null) { - final boolean isAppForeground = - mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND; - mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground, - SystemClock.elapsedRealtime())); - } - } - // Clears the 'fake' auto-group summary. @VisibleForTesting @GuardedBy("mNotificationLock") @@ -5997,7 +5985,7 @@ public class NotificationManagerService extends SystemService { // Creates a 'fake' summary for a package that has exceeded the solo-notification limit. NotificationRecord createAutoGroupSummary(int userId, String pkg, String triggeringKey, - boolean needsOngoingFlag) { + int flagsToSet) { NotificationRecord summaryRecord = null; boolean isPermissionFixed = mPermissionHelper.isPermissionFixed(pkg, userId); synchronized (mNotificationLock) { @@ -6007,7 +5995,6 @@ public class NotificationManagerService extends SystemService { // adjustment will post a summary if needed. return null; } - NotificationChannel channel = notificationRecord.getChannel(); final StatusBarNotification adjustedSbn = notificationRecord.getSbn(); userId = adjustedSbn.getUser().getIdentifier(); int uid = adjustedSbn.getUid(); @@ -6030,11 +6017,8 @@ public class NotificationManagerService extends SystemService { .setGroupSummary(true) .setGroupAlertBehavior(Notification.GROUP_ALERT_CHILDREN) .setGroup(GroupHelper.AUTOGROUP_KEY) - .setFlag(FLAG_AUTOGROUP_SUMMARY, true) - .setFlag(Notification.FLAG_GROUP_SUMMARY, true) - .setFlag(FLAG_ONGOING_EVENT, needsOngoingFlag) + .setFlag(flagsToSet, true) .setColor(adjustedSbn.getNotification().color) - .setLocalOnly(true) .build(); summaryNotification.extras.putAll(extras); Intent appIntent = getContext().getPackageManager().getLaunchIntentForPackage(pkg); @@ -6372,6 +6356,7 @@ public class NotificationManagerService extends SystemService { * The private API only accessible to the system process. */ private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() { + @Override public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId) { @@ -7827,18 +7812,17 @@ public class NotificationManagerService extends SystemService { if (notification.getSmallIcon() != null) { StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null; mListeners.notifyPostedLocked(r, old); - if ((oldSbn == null || !Objects.equals(oldSbn.getGroup(), n.getGroup())) - && !isCritical(r)) { - mHandler.post(() -> { - synchronized (mNotificationLock) { - mGroupHelper.onNotificationPosted( - n, hasAutoGroupSummaryLocked(n)); - } - }); - } else if (oldSbn != null) { - final NotificationRecord finalRecord = r; - mHandler.post(() -> - mGroupHelper.onNotificationUpdated(finalRecord.getSbn())); + if (oldSbn == null + || !Objects.equals(oldSbn.getGroup(), n.getGroup()) + || oldSbn.getNotification().flags != n.getNotification().flags) { + if (!isCritical(r)) { + mHandler.post(() -> { + synchronized (mNotificationLock) { + mGroupHelper.onNotificationPosted( + n, hasAutoGroupSummaryLocked(n)); + } + }); + } } } else { Slog.e(TAG, "Not posting notification without small icon: " + notification); diff --git a/services/core/java/com/android/server/notification/ZenLog.java b/services/core/java/com/android/server/notification/ZenLog.java index 35b94e7ccd63..88d23ce3f9a1 100644 --- a/services/core/java/com/android/server/notification/ZenLog.java +++ b/services/core/java/com/android/server/notification/ZenLog.java @@ -27,6 +27,7 @@ import android.service.notification.Condition; import android.service.notification.IConditionProvider; import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeDiff; import android.util.Log; import android.util.Slog; @@ -146,13 +147,13 @@ public class ZenLog { public static void traceConfig(String reason, ZenModeConfig oldConfig, ZenModeConfig newConfig) { - ZenModeConfig.Diff diff = ZenModeConfig.diff(oldConfig, newConfig); - if (diff.isEmpty()) { + ZenModeDiff.ConfigDiff diff = new ZenModeDiff.ConfigDiff(oldConfig, newConfig); + if (diff == null || !diff.hasDiff()) { append(TYPE_CONFIG, reason + " no changes"); } else { append(TYPE_CONFIG, reason + ",\n" + (newConfig != null ? newConfig.toString() : null) - + ",\n" + ZenModeConfig.diff(oldConfig, newConfig)); + + ",\n" + diff); } } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 596e9b964643..69ef3f780172 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); + mPm.mSettings, mContext); } catch (ReconcileFailure e) { for (InstallRequest request : requests) { request.setError("Reconciliation failed...", e); @@ -3930,7 +3930,7 @@ final class InstallPackageHelper { mPm.mPackages, Collections.singletonMap(pkgName, mPm.getSettingsVersionForPackage(parsedPackage)), mSharedLibraries, mPm.mSettings.getKeySetManagerService(), - mPm.mSettings); + mPm.mSettings, mContext); if ((scanFlags & SCAN_AS_APEX) == 0) { appIdCreated = optimisticallyRegisterAppId(installRequest); } else { diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index 5312ae6ca84c..e3c97e933ad1 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -16,6 +16,7 @@ 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; @@ -23,19 +24,24 @@ 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; @@ -54,7 +60,7 @@ final class ReconcilePackageUtils { Map<String, AndroidPackage> allPackages, Map<String, Settings.VersionInfo> versionInfos, SharedLibrariesImpl sharedLibraries, - KeySetManagerService ksms, Settings settings) + KeySetManagerService ksms, Settings settings, Context context) throws ReconcileFailure { final List<ReconciledPackage> result = new ArrayList<>(installRequests.size()); @@ -143,11 +149,11 @@ final class ReconcilePackageUtils { } else { if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) { throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE, - "Package " + parsedPackage.getPackageName() + "Package " + installPackageName + " upgrade keys do not match the previously installed" + " version"); } else { - String msg = "System package " + parsedPackage.getPackageName() + String msg = "System package " + installPackageName + " signature changed; retaining data."; PackageManagerService.reportSettingsProblem(Log.WARN, msg); } @@ -168,11 +174,42 @@ final class ReconcilePackageUtils { removeAppKeySetData = true; } - // 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 + // 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 // 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 @@ -189,7 +226,7 @@ final class ReconcilePackageUtils { for (AndroidPackage androidPackage : sharedUserSetting.getPackages()) { if (androidPackage.getPackageName() != null && !androidPackage.getPackageName().equals( - parsedPackage.getPackageName())) { + installPackageName)) { mergedDetails = mergedDetails.mergeLineageWith( androidPackage.getSigningDetails(), MERGE_RESTRICTED_CAPABILITY); @@ -219,7 +256,7 @@ final class ReconcilePackageUtils { if (sharedUserSetting != null) { if (sharedUserSetting.signaturesChanged != null && !PackageManagerServiceUtils.canJoinSharedUserId( - parsedPackage.getPackageName(), parsedPackage.getSigningDetails(), + installPackageName, parsedPackage.getSigningDetails(), sharedUserSetting, PackageManagerServiceUtils.SHARED_USER_ID_JOIN_TYPE_SYSTEM)) { if (SystemProperties.getInt("ro.product.first_api_level", 0) <= 29) { @@ -240,7 +277,7 @@ final class ReconcilePackageUtils { // whichever package happened to be scanned later. throw new IllegalStateException( "Signature mismatch on system package " - + parsedPackage.getPackageName() + + installPackageName + " for shared user " + sharedUserSetting); } @@ -252,7 +289,7 @@ final class ReconcilePackageUtils { sharedUserSetting.signaturesChanged = Boolean.TRUE; } // File a report about this. - String msg = "System package " + parsedPackage.getPackageName() + String msg = "System package " + installPackageName + " signature changed; retaining data."; PackageManagerService.reportSettingsProblem(Log.WARN, msg); } catch (IllegalArgumentException e) { diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index e5e32f0a9690..4d2b119d7800 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -206,8 +206,6 @@ final class DefaultPermissionGrantPolicy { static { SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS); SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND); - SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE); - SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND); } private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>(); diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java index c5f939a2a66e..297ad73e054b 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java @@ -1332,9 +1332,7 @@ public class PermissionManagerService extends IPermissionManager.Stub { // Bg location is one-off runtime modifier permission and has no app op if (sPlatformPermissions.containsKey(permission) && !Manifest.permission.ACCESS_BACKGROUND_LOCATION.equals(permission) - && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission) - && !Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND - .equals(permission)) { + && !Manifest.permission.BODY_SENSORS_BACKGROUND.equals(permission)) { Slog.wtf(LOG_TAG, "Platform runtime permission " + permission + " with no app op defined!"); } diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index cc2c9adfcba4..3492b2660c4a 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -23,6 +23,7 @@ import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.MODE_ERRORED; import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT; +import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED; import static android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED; import static android.content.pm.PackageManager.FLAGS_PERMISSION_RESTRICTION_ANY_EXEMPT; import static android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION; @@ -3655,6 +3656,26 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt for (String permission : pkg.getRequestedPermissions()) { Integer permissionState = permissionStates.get(permission); + + if (Objects.equals(permission, Manifest.permission.USE_FULL_SCREEN_INTENT) + && permissionState == null) { + final PackageStateInternal ps; + final long token = Binder.clearCallingIdentity(); + try { + ps = mPackageManagerInt.getPackageStateInternal(pkg.getPackageName()); + } finally { + Binder.restoreCallingIdentity(token); + } + final String[] useFullScreenIntentPackageNames = + mContext.getResources().getStringArray( + com.android.internal.R.array.config_useFullScreenIntentPackages); + final boolean canUseFullScreenIntent = (ps != null && ps.isSystem()) + || ArrayUtils.contains(useFullScreenIntentPackageNames, + pkg.getPackageName()); + permissionState = canUseFullScreenIntent ? PERMISSION_STATE_GRANTED + : PERMISSION_STATE_DENIED; + } + if (permissionState == null || permissionState == PERMISSION_STATE_DEFAULT) { continue; } diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java index 401eac6e2d19..7a5664f8135e 100644 --- a/services/core/java/com/android/server/policy/AppOpsPolicy.java +++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java @@ -316,6 +316,7 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat private int resolveDatasourceOp(int code, int uid, @NonNull String packageName, @Nullable String attributionTag) { code = resolveRecordAudioOp(code, uid); + code = resolveSandboxedServiceOp(code, uid); if (attributionTag == null) { return code; } @@ -439,6 +440,28 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat return code; } + private int resolveSandboxedServiceOp(int code, int uid) { + if (!Process.isIsolated(uid) // simple check which fails-fast for the common case + || !(code == AppOpsManager.OP_RECORD_AUDIO || code == AppOpsManager.OP_CAMERA)) { + return code; + } + final HotwordDetectionServiceIdentity hotwordDetectionServiceIdentity = + mVoiceInteractionManagerInternal.getHotwordDetectionServiceIdentity(); + if (hotwordDetectionServiceIdentity != null + && uid == hotwordDetectionServiceIdentity.getIsolatedUid()) { + // Upgrade the op such that no indicators is shown for camera or audio service. This + // will bypass the permission checking for the original OP_RECORD_AUDIO and OP_CAMERA. + switch (code) { + case AppOpsManager.OP_RECORD_AUDIO: + return AppOpsManager.OP_RECORD_AUDIO_SANDBOXED; + case AppOpsManager.OP_CAMERA: + return AppOpsManager.OP_CAMERA_SANDBOXED; + } + } + return code; + } + + private int resolveUid(int code, int uid) { // The HotwordDetectionService is an isolated service, which ordinarily cannot hold // permissions. So we allow it to assume the owning package identity for certain diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 8165958bd4ef..fc6b4e9dcb20 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -222,6 +222,7 @@ import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener; import com.android.server.policy.keyguard.KeyguardStateMonitor.StateCallback; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.vr.VrManagerInternal; +import com.android.server.wallpaper.WallpaperManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.DisplayPolicy; import com.android.server.wm.DisplayRotation; @@ -412,6 +413,9 @@ public class PhoneWindowManager implements WindowManagerPolicy { SensorPrivacyManager mSensorPrivacyManager; DisplayManager mDisplayManager; DisplayManagerInternal mDisplayManagerInternal; + + private WallpaperManagerInternal mWallpaperManagerInternal; + boolean mPreloadedRecentApps; final Object mServiceAcquireLock = new Object(); Vibrator mVibrator; // Vibrator for giving feedback of orientation changes @@ -5016,11 +5020,34 @@ public class PhoneWindowManager implements WindowManagerPolicy { return bootCompleted ? mKeyguardDrawnTimeout : 5000; } + @Nullable + private WallpaperManagerInternal getWallpaperManagerInternal() { + if (mWallpaperManagerInternal == null) { + mWallpaperManagerInternal = LocalServices.getService(WallpaperManagerInternal.class); + } + return mWallpaperManagerInternal; + } + + private void reportScreenTurningOnToWallpaper(int displayId) { + WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal(); + if (wallpaperManagerInternal != null) { + wallpaperManagerInternal.onScreenTurningOn(displayId); + } + } + + private void reportScreenTurnedOnToWallpaper(int displayId) { + WallpaperManagerInternal wallpaperManagerInternal = getWallpaperManagerInternal(); + if (wallpaperManagerInternal != null) { + wallpaperManagerInternal.onScreenTurnedOn(displayId); + } + } + // Called on the DisplayManager's DisplayPowerController thread. @Override public void screenTurningOn(int displayId, final ScreenOnListener screenOnListener) { if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turning on..."); + reportScreenTurningOnToWallpaper(displayId); if (displayId == DEFAULT_DISPLAY) { Trace.asyncTraceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "screenTurningOn", 0 /* cookie */); @@ -5061,6 +5088,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { public void screenTurnedOn(int displayId) { if (DEBUG_WAKEUP) Slog.i(TAG, "Display " + displayId + " turned on..."); + reportScreenTurnedOnToWallpaper(displayId); + if (displayId != DEFAULT_DISPLAY) { return; } 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/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java index 7beb1edfe51f..e437be8e01b5 100644 --- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java +++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java @@ -105,36 +105,46 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { @Override public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage, @FailureReasons int failureReason, int mitigationCount) { - // For native crashes, we will roll back any available rollbacks + boolean anyRollbackAvailable = !mContext.getSystemService(RollbackManager.class) + .getAvailableRollbacks().isEmpty(); + int impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; + if (failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH - && !mContext.getSystemService(RollbackManager.class) - .getAvailableRollbacks().isEmpty()) { - return PackageHealthObserverImpact.USER_IMPACT_MEDIUM; - } - if (getAvailableRollback(failedPackage) == null) { - // Don't handle the notification, no rollbacks available for the package - return PackageHealthObserverImpact.USER_IMPACT_NONE; - } else { + && anyRollbackAvailable) { + // For native crashes, we will directly roll back any available rollbacks + // Note: For non-native crashes the rollback-all step has higher impact + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; + } else if (mitigationCount == 1 && getAvailableRollback(failedPackage) != null) { // Rollback is available, we may get a callback into #execute - return PackageHealthObserverImpact.USER_IMPACT_MEDIUM; + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; + } else if (mitigationCount > 1 && anyRollbackAvailable) { + // If any rollbacks are available, we will commit them + impact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_70; } + + return impact; } @Override public boolean execute(@Nullable VersionedPackage failedPackage, @FailureReasons int rollbackReason, int mitigationCount) { if (rollbackReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) { - mHandler.post(() -> rollbackAll()); + mHandler.post(() -> rollbackAll(rollbackReason)); return true; } - RollbackInfo rollback = getAvailableRollback(failedPackage); - if (rollback == null) { - Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage); - return false; + if (mitigationCount == 1) { + RollbackInfo rollback = getAvailableRollback(failedPackage); + if (rollback == null) { + Slog.w(TAG, "Expected rollback but no valid rollback found for " + failedPackage); + return false; + } + mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason)); + } else if (mitigationCount > 1) { + mHandler.post(() -> rollbackAll(rollbackReason)); } - mHandler.post(() -> rollbackPackage(rollback, failedPackage, rollbackReason)); - // Assume rollback executed successfully + + // Assume rollbacks executed successfully return true; } @@ -468,7 +478,7 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { } @WorkerThread - private void rollbackAll() { + private void rollbackAll(@FailureReasons int rollbackReason) { assertInWorkerThread(); RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class); List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks(); @@ -487,7 +497,7 @@ final class RollbackPackageHealthObserver implements PackageHealthObserver { for (RollbackInfo rollback : rollbacks) { VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom(); - rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); + rollbackPackage(rollback, sample, rollbackReason); } } } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java index 584fbddee478..3699557706fd 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerInternal.java @@ -27,4 +27,10 @@ public abstract class WallpaperManagerInternal { * Notifies the display is ready for adding wallpaper on it. */ public abstract void onDisplayReady(int displayId); + + /** Notifies when the screen finished turning on and is visible to the user. */ + public abstract void onScreenTurnedOn(int displayId); + + /** Notifies when the screen starts turning on and is not yet visible to the user. */ + public abstract void onScreenTurningOn(int displayId); } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 4a03628ab8e5..e17866922990 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1613,6 +1613,15 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public void onDisplayReady(int displayId) { onDisplayReadyInternal(displayId); } + + @Override + public void onScreenTurnedOn(int displayId) { + notifyScreenTurnedOn(displayId); + } + @Override + public void onScreenTurningOn(int displayId) { + notifyScreenTurningOn(displayId); + } } void initialize() { @@ -2442,6 +2451,54 @@ public class WallpaperManagerService extends IWallpaperManager.Stub } } + /** + * Propagates screen turned on event to wallpaper engine. + */ + @Override + public void notifyScreenTurnedOn(int displayId) { + synchronized (mLock) { + final WallpaperData data = mWallpaperMap.get(mCurrentUserId); + if (data != null + && data.connection != null + && data.connection.containsDisplay(displayId)) { + final IWallpaperEngine engine = data.connection + .getDisplayConnectorOrCreate(displayId).mEngine; + if (engine != null) { + try { + engine.onScreenTurnedOn(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + } + } + + + + /** + * Propagate screen turning on event to wallpaper engine. + */ + @Override + public void notifyScreenTurningOn(int displayId) { + synchronized (mLock) { + final WallpaperData data = mWallpaperMap.get(mCurrentUserId); + if (data != null + && data.connection != null + && data.connection.containsDisplay(displayId)) { + final IWallpaperEngine engine = data.connection + .getDisplayConnectorOrCreate(displayId).mEngine; + if (engine != null) { + try { + engine.onScreenTurningOn(); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + } + } + @Override public boolean setLockWallpaperCallback(IWallpaperManagerCallback cb) { checkPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW); diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index df360b86fdf8..f3001133338a 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -25,6 +25,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFI import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY; +import static com.android.internal.util.DumpUtils.dumpSparseArray; +import static com.android.internal.util.DumpUtils.dumpSparseArrayValues; import static com.android.server.accessibility.AccessibilityTraceFileProto.ENTRY; import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER; import static com.android.server.accessibility.AccessibilityTraceFileProto.MAGIC_NUMBER_H; @@ -44,8 +46,6 @@ import static com.android.server.accessibility.AccessibilityTraceProto.WHERE; import static com.android.server.accessibility.AccessibilityTraceProto.WINDOW_MANAGER_SERVICE; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.dumpSparseArray; -import static com.android.server.wm.WindowManagerService.dumpSparseArrayValues; import static com.android.server.wm.WindowTracing.WINSCOPE_EXT; import android.accessibilityservice.AccessibilityTrace; diff --git a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java index afe164056ff4..70f20075b48c 100644 --- a/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java +++ b/services/core/java/com/android/server/wm/AccessibilityWindowsPopulator.java @@ -16,8 +16,9 @@ package com.android.server.wm; -import static com.android.server.wm.WindowManagerService.ValueDumper; -import static com.android.server.wm.WindowManagerService.dumpSparseArray; +import static com.android.internal.util.DumpUtils.KeyDumper; +import static com.android.internal.util.DumpUtils.ValueDumper; +import static com.android.internal.util.DumpUtils.dumpSparseArray; import static com.android.server.wm.utils.RegionUtils.forEachRect; import android.annotation.NonNull; @@ -41,7 +42,6 @@ import android.view.WindowManager; import android.window.WindowInfosListener; import com.android.internal.annotations.GuardedBy; -import com.android.server.wm.WindowManagerService.KeyDumper; import java.io.PrintWriter; import java.util.ArrayList; diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 62144401223b..7718dd88b772 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -1145,8 +1145,7 @@ class ActivityClientController extends IActivityClientController.Stub { if (mService.mWindowManager.mSyncEngine.hasActiveSync()) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Pending Multiwindow Fullscreen Request: %s", transition); - mService.mWindowManager.mSyncEngine.queueSyncSet( - () -> r.mTransitionController.moveToCollecting(transition), + r.mTransitionController.queueCollecting(transition, () -> { executeFullscreenRequestTransition(fullscreenRequest, callback, r, transition, true /* queued */); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 1bcc05e6f116..0b98495c8e99 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -9820,8 +9820,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A scheduleStopForRestartProcess(); }; if (mWmService.mSyncEngine.hasActiveSync()) { - mWmService.mSyncEngine.queueSyncSet( - () -> mTransitionController.moveToCollecting(transition), executeRestart); + mTransitionController.queueCollecting(transition, executeRestart); } else { mTransitionController.moveToCollecting(transition); executeRestart.run(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 12fe6a0dba25..e780716dd06c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -831,7 +831,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { private final Runnable mUpdateOomAdjRunnable = new Runnable() { @Override public void run() { - mAmInternal.updateOomAdj(); + mAmInternal.updateOomAdj(ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY); } }; @@ -2874,8 +2874,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */, getTransitionController(), mWindowManager.mSyncEngine); if (mWindowManager.mSyncEngine.hasActiveSync()) { - mWindowManager.mSyncEngine.queueSyncSet( - () -> getTransitionController().moveToCollecting(transition), + getTransitionController().queueCollecting(transition, () -> { if (!task.getWindowConfiguration().canResizeTask()) { Slog.w(TAG, "resizeTask not allowed on task=" + task); @@ -3629,9 +3628,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Pending Pip-Enter: %s", transition); - mWindowManager.mSyncEngine.queueSyncSet( - () -> getTransitionController().moveToCollecting(transition), - enterPipRunnable); + getTransitionController().queueCollecting(transition, enterPipRunnable); } else { // Move to collecting immediately to "claim" the sync-engine for this // transition. @@ -3647,9 +3644,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Pending Pip-Enter: %s", transition); - mWindowManager.mSyncEngine.queueSyncSet( - () -> getTransitionController().moveToCollecting(transition), - enterPipRunnable); + getTransitionController().queueCollecting(transition, enterPipRunnable); } else { if (transition != null) { getTransitionController().moveToCollecting(transition); diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 7b562b0bc964..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 @@ -648,31 +645,28 @@ class BackNavigationController { if (finishedTransition == mWaitTransitionFinish) { clearBackAnimations(); } + if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) { return false; } - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Handling the deferred animation after transition finished"); - // Show the target surface and its parents to prevent it or its parents hidden when - // the transition finished. - // The target could be affected by transition when : - // Open transition -> the open target in back navigation - // Close transition -> the close target in back navigation. + // Find the participated container collected by transition when : + // Open transition -> the open target in back navigation, the close target in transition. + // Close transition -> the close target in back navigation, the open target in transition. boolean hasTarget = false; - final SurfaceControl.Transaction t = - mPendingAnimationBuilder.mCloseTarget.getPendingTransaction(); - for (int i = 0; i < targets.size(); i++) { - final WindowContainer wc = targets.get(i).mContainer; - if (wc.asActivityRecord() == null && wc.asTask() == null) { - continue; - } else if (!mPendingAnimationBuilder.containTarget(wc)) { + for (int i = 0; i < finishedTransition.mParticipants.size(); i++) { + final WindowContainer wc = finishedTransition.mParticipants.valueAt(i); + if (wc.asActivityRecord() == null && wc.asTask() == null + && wc.asTaskFragment() == null) { continue; } - hasTarget = true; - t.show(wc.getSurfaceControl()); + if (mPendingAnimationBuilder.containTarget(wc)) { + hasTarget = true; + break; + } } if (!hasTarget) { @@ -680,20 +674,33 @@ 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; } + // Ensure the final animation targets which hidden by transition could be visible. + for (int i = 0; i < targets.size(); i++) { + final WindowContainer wc = targets.get(i).mContainer; + wc.prepareSurfaces(); + } + scheduleAnimation(mPendingAnimationBuilder); mPendingAnimationBuilder = null; 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. */ @@ -1076,7 +1083,7 @@ class BackNavigationController { boolean containTarget(@NonNull WindowContainer wc) { return wc == mOpenTarget || wc == mCloseTarget - || wc.hasChild(mOpenTarget) || wc.hasChild(mCloseTarget); + || mOpenTarget.hasChild(wc) || mCloseTarget.hasChild(wc); } /** @@ -1151,6 +1158,11 @@ class BackNavigationController { private static void setLaunchBehind(@NonNull ActivityRecord activity) { if (!activity.isVisibleRequested()) { activity.setVisibility(true); + // The transition could commit the visibility and in the finishing state, that could + // skip commitVisibility call in setVisibility cause the activity won't visible here. + // Call it again to make sure the activity could be visible while handling the pending + // animation. + activity.commitVisibility(true, true); } activity.mLaunchTaskBehind = true; diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 57fca3aeea79..237846997e9e 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -146,8 +146,6 @@ public class RecentsAnimationController implements DeathRecipient { @VisibleForTesting boolean mIsAddingTaskToTargets; - @VisibleForTesting - boolean mShouldAttachNavBarToAppDuringTransition; private boolean mNavigationBarAttachedToApp; private ActivityRecord mNavBarAttachedApp; @@ -379,8 +377,6 @@ public class RecentsAnimationController implements DeathRecipient { mDisplayId = displayId; mStatusBar = LocalServices.getService(StatusBarManagerInternal.class); mDisplayContent = service.mRoot.getDisplayContent(displayId); - mShouldAttachNavBarToAppDuringTransition = - mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition(); } /** @@ -577,7 +573,7 @@ public class RecentsAnimationController implements DeathRecipient { } private void attachNavigationBarToApp() { - if (!mShouldAttachNavBarToAppDuringTransition + if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition() // Skip the case where the nav bar is controlled by fade rotation. || mDisplayContent.getAsyncRotationController() != null) { return; @@ -652,7 +648,7 @@ public class RecentsAnimationController implements DeathRecipient { } void animateNavigationBarForAppLaunch(long duration) { - if (!mShouldAttachNavBarToAppDuringTransition + if (!mDisplayContent.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition() // Skip the case where the nav bar is controlled by fade rotation. || mDisplayContent.getAsyncRotationController() != null || mNavigationBarAttachedToApp diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 07daa4b22ac9..ad934541267e 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2335,9 +2335,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> transition.playNow(); }; if (display.mTransitionController.isCollecting()) { - mWmService.mSyncEngine.queueSyncSet( - () -> display.mTransitionController.moveToCollecting(transition), - sendSleepTransition); + display.mTransitionController.queueCollecting(transition, sendSleepTransition); } else { display.mTransitionController.moveToCollecting(transition); sendSleepTransition.run(); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index fb592e124920..89f975387667 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -5635,8 +5635,7 @@ class Task extends TaskFragment { if (mWmService.mSyncEngine.hasActiveSync()) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Pending Move-to-back: %s", transition); - mWmService.mSyncEngine.queueSyncSet( - () -> mTransitionController.moveToCollecting(transition), + mTransitionController.queueCollecting(transition, () -> { // Need to check again since this happens later and the system might // be in a different state. @@ -5684,6 +5683,7 @@ class Task extends TaskFragment { // Usually resuming a top activity triggers the next app transition, but nothing's got // resumed in this case, so we need to execute it explicitly. mDisplayContent.executeAppTransition(); + mDisplayContent.setFocusedApp(topActivity); } else { mRootWindowContainer.resumeFocusedTasksTopActivities(); } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 3a909cedc5ab..652c2977f40e 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) @@ -1609,7 +1620,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" id=" + mSyncId); sb.append(" type=" + transitTypeToString(mType)); - sb.append(" flags=" + mFlags); + sb.append(" flags=0x" + Integer.toHexString(mFlags)); sb.append('}'); return sb.toString(); } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 3bf896939f49..4c1c2ff1ac10 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -443,26 +443,36 @@ class TransitionController { return type == TRANSIT_OPEN || type == TRANSIT_CLOSE; } - /** Whether the display change should run with blast sync. */ - private static boolean shouldSync(@NonNull TransitionRequestInfo.DisplayChange displayChange) { - if ((displayChange.getStartRotation() + displayChange.getEndRotation()) % 2 == 0) { + /** Sets the sync method for the display change. */ + private void setDisplaySyncMethod(@NonNull TransitionRequestInfo.DisplayChange displayChange, + @NonNull Transition displayTransition, @NonNull DisplayContent displayContent) { + final int startRotation = displayChange.getStartRotation(); + final int endRotation = displayChange.getEndRotation(); + if (startRotation != endRotation && (startRotation + endRotation) % 2 == 0) { // 180 degrees rotation change may not change screen size. So the clients may draw // some frames before and after the display projection transaction is applied by the // remote player. That may cause some buffers to show in different rotation. So use // sync method to pause clients drawing until the projection transaction is applied. - return true; + mAtm.mWindowManager.mSyncEngine.setSyncMethod(displayTransition.getSyncId(), + BLASTSyncEngine.METHOD_BLAST); } final Rect startBounds = displayChange.getStartAbsBounds(); final Rect endBounds = displayChange.getEndAbsBounds(); - if (startBounds == null || endBounds == null) return false; + if (startBounds == null || endBounds == null) return; final int startWidth = startBounds.width(); final int startHeight = startBounds.height(); final int endWidth = endBounds.width(); final int endHeight = endBounds.height(); // This is changing screen resolution. Because the screen decor layers are excluded from // screenshot, their draw transactions need to run with the start transaction. - return (endWidth > startWidth) == (endHeight > startHeight) - && (endWidth != startWidth || endHeight != startHeight); + if ((endWidth > startWidth) == (endHeight > startHeight) + && (endWidth != startWidth || endHeight != startHeight)) { + displayContent.forAllWindows(w -> { + if (w.mToken.mRoundedCornerOverlay && w.mHasSurface) { + w.mSyncMethodOverride = BLASTSyncEngine.METHOD_BLAST; + } + }, true /* traverseTopToBottom */); + } } /** @@ -494,9 +504,9 @@ class TransitionController { } else { newTransition = requestStartTransition(createTransition(type, flags), trigger != null ? trigger.asTask() : null, remoteTransition, displayChange); - if (newTransition != null && displayChange != null && shouldSync(displayChange)) { - mAtm.mWindowManager.mSyncEngine.setSyncMethod(newTransition.getSyncId(), - BLASTSyncEngine.METHOD_BLAST); + if (newTransition != null && displayChange != null && trigger != null + && trigger.asDisplayContent() != null) { + setDisplaySyncMethod(displayChange, newTransition, trigger.asDisplayContent()); } } if (trigger != null) { @@ -874,6 +884,15 @@ 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); + } + /** * This manages the animating state of processes that are running remote animations for * {@link #mTransitionPlayerProc}. diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 82c057b99c10..8fecf111d98c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -235,7 +235,6 @@ import android.util.EventLog; import android.util.MergedConfiguration; import android.util.Pair; import android.util.Slog; -import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.util.TimeUtils; @@ -9460,53 +9459,4 @@ public class WindowManagerService extends IWindowManager.Stub return List.copyOf(notifiedApps); } } - - // TODO(b/271188189): move dump stuff below to common code / add unit tests - - interface ValueDumper<T> { - void dump(T value); - } - - interface KeyDumper{ - void dump(int index, int key); - } - - static void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<?> array, String name) { - dumpSparseArray(pw, prefix, array, name, /* keyDumper= */ null, /* valuedumper= */ null); - } - - static <T> void dumpSparseArrayValues(PrintWriter pw, String prefix, SparseArray<T> array, - String name) { - dumpSparseArray(pw, prefix, array, name, (i, k) -> {}, /* valueDumper= */ null); - } - - static <T> void dumpSparseArray(PrintWriter pw, String prefix, SparseArray<T> array, - String name, @Nullable KeyDumper keyDumper, @Nullable ValueDumper<T> valueDumper) { - int size = array.size(); - if (size == 0) { - pw.print(prefix); pw.print("No "); pw.print(name); pw.println("s"); - return; - } - pw.print(prefix); pw.print(size); pw.print(' '); - pw.print(name); pw.print(size > 1 ? "s" : ""); pw.println(':'); - - String prefix2 = prefix + " "; - for (int i = 0; i < size; i++) { - int key = array.keyAt(i); - T value = array.valueAt(i); - if (keyDumper != null) { - keyDumper.dump(i, key); - } else { - pw.print(prefix2); pw.print(i); pw.print(": "); pw.print(key); pw.print("->"); - } - if (value == null) { - pw.print("(null)"); - } else if (valueDumper != null) { - valueDumper.dump(value); - } else { - pw.print(value); - } - pw.println(); - } - } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 32d54d774b40..f63470f2bea4 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -291,23 +291,21 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub 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 = new Transition(type, 0 /* flags */, - mTransitionController, mService.mWindowManager.mSyncEngine); + final Transition nextTransition = transition; ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Pending Transition: %s", nextTransition); - mService.mWindowManager.mSyncEngine.queueSyncSet( - // Make sure to collect immediately to prevent another transition - // from sneaking in before it. Note: moveToCollecting internally - // calls startSyncSet. - () -> mTransitionController.moveToCollecting(nextTransition), + mTransitionController.queueCollecting(nextTransition, () -> { nextTransition.start(); + nextTransition.mLogger.mStartWCT = wct; applyTransaction(wct, -1 /*syncId*/, nextTransition, caller); if (needsSetReady) { nextTransition.setAllReady(); @@ -315,7 +313,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub }); return nextTransition.getToken(); } - transition = mTransitionController.createTransition(type); + mTransitionController.moveToCollecting(transition); } if (!transition.isCollecting() && !transition.isForcePlaying()) { Slog.e(TAG, "Trying to start a transition that isn't collecting. This probably" @@ -474,11 +472,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mTransitionController, mService.mWindowManager.mSyncEngine); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Creating Pending Transition for TaskFragment: %s", nextTransition); - mService.mWindowManager.mSyncEngine.queueSyncSet( - // Make sure to collect immediately to prevent another transition - // from sneaking in before it. Note: moveToCollecting internally - // calls startSyncSet. - () -> mTransitionController.moveToCollecting(nextTransition), + mTransitionController.queueCollecting(nextTransition, () -> { if (mTaskFragmentOrganizerController.isValidTransaction(wct) && (applyTransaction(wct, -1 /* syncId */, nextTransition, caller) diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java index a04143afadcd..5a718086d71c 100644 --- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java @@ -89,14 +89,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/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java index aeb4801628f2..3dba4a929859 100644 --- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java +++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java @@ -89,11 +89,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); @@ -161,7 +163,8 @@ public class GetRequestSession extends RequestSession<GetCredentialRequest, @Override public void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName, ProviderSession.CredentialsSource source) { - Slog.d(TAG, "in onStatusChanged with status: " + status + ", and source: " + source); + Slog.d(TAG, "in onStatusChanged for: " + componentName + ", with status: " + + status + ", and source: " + source); // Auth entry was selected, and it did not have any underlying credentials if (status == ProviderSession.Status.NO_CREDENTIALS_FROM_AUTH_ENTRY) { diff --git a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java index 0c3d3f4ed6d2..9ec0ecd93b3c 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderClearSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderClearSession.java @@ -26,7 +26,6 @@ import android.credentials.ui.ProviderPendingIntentResponse; import android.os.ICancellationSignal; import android.service.credentials.CallingAppInfo; import android.service.credentials.ClearCredentialStateRequest; -import android.util.Log; import android.util.Slog; /** @@ -81,7 +80,7 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS @Override public void onProviderResponseSuccess(@Nullable Void response) { - Log.i(TAG, "in onProviderResponseSuccess"); + Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName); mProviderResponseSet = true; updateStatusAndInvokeCallback(Status.COMPLETE, /*source=*/ CredentialsSource.REMOTE_PROVIDER); @@ -105,7 +104,7 @@ public final class ProviderClearSession extends ProviderSession<ClearCredentialS updateStatusAndInvokeCallback(Status.SERVICE_DEAD, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } else { - Slog.i(TAG, "Component names different in onProviderServiceDied - " + Slog.w(TAG, "Component names different in onProviderServiceDied - " + "this should not happen"); } } diff --git a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java index 8b9255a9c56b..09433dbb0c52 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderCreateSession.java @@ -37,7 +37,6 @@ import android.service.credentials.CreateCredentialRequest; import android.service.credentials.CreateEntry; import android.service.credentials.CredentialProviderService; import android.service.credentials.RemoteEntry; -import android.util.Log; import android.util.Pair; import android.util.Slog; @@ -93,7 +92,8 @@ public final class ProviderCreateSession extends ProviderSession< createRequestSession.mHybridService ); } - Log.i(TAG, "Unable to create provider session"); + Slog.d(TAG, "Unable to create provider session for: " + + providerInfo.getComponentName()); return null; } @@ -122,7 +122,6 @@ public final class ProviderCreateSession extends ProviderSession< return new CreateCredentialRequest(callingAppInfo, capability, clientRequest.getCredentialData()); } - Log.i(TAG, "Unable to create provider request - capabilities do not match"); return null; } @@ -146,7 +145,7 @@ public final class ProviderCreateSession extends ProviderSession< @Override public void onProviderResponseSuccess( @Nullable BeginCreateCredentialResponse response) { - Log.i(TAG, "in onProviderResponseSuccess"); + Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName); onSetInitialRemoteResponse(response); } @@ -169,7 +168,7 @@ public final class ProviderCreateSession extends ProviderSession< updateStatusAndInvokeCallback(Status.SERVICE_DEAD, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } else { - Slog.i(TAG, "Component names different in onProviderServiceDied - " + Slog.w(TAG, "Component names different in onProviderServiceDied - " + "this should not happen"); } } @@ -180,7 +179,6 @@ public final class ProviderCreateSession extends ProviderSession< } private void onSetInitialRemoteResponse(BeginCreateCredentialResponse response) { - Log.i(TAG, "onSetInitialRemoteResponse with save entries"); mProviderResponse = response; mProviderResponseDataHandler.addResponseContent(response.getCreateEntries(), response.getRemoteCreateEntry()); @@ -199,14 +197,12 @@ public final class ProviderCreateSession extends ProviderSession< @Nullable protected CreateCredentialProviderData prepareUiData() throws IllegalArgumentException { - Log.i(TAG, "In prepareUiData"); if (!ProviderSession.isUiInvokingStatus(getStatus())) { - Log.i(TAG, "In prepareUiData not in uiInvokingStatus"); + Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString()); return null; } if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) { - Log.i(TAG, "In prepareUiData save entries not null"); return mProviderResponseDataHandler.toCreateCredentialProviderData(); } return null; @@ -218,7 +214,7 @@ public final class ProviderCreateSession extends ProviderSession< switch (entryType) { case SAVE_ENTRY_KEY: if (mProviderResponseDataHandler.getCreateEntry(entryKey) == null) { - Log.i(TAG, "Unexpected save entry key"); + Slog.w(TAG, "Unexpected save entry key"); invokeCallbackOnInternalInvalidState(); return; } @@ -226,14 +222,14 @@ public final class ProviderCreateSession extends ProviderSession< break; case REMOTE_ENTRY_KEY: if (mProviderResponseDataHandler.getRemoteEntry(entryKey) == null) { - Log.i(TAG, "Unexpected remote entry key"); + Slog.w(TAG, "Unexpected remote entry key"); invokeCallbackOnInternalInvalidState(); return; } onRemoteEntrySelected(providerPendingIntentResponse); break; default: - Log.i(TAG, "Unsupported entry type selected"); + Slog.w(TAG, "Unsupported entry type selected"); invokeCallbackOnInternalInvalidState(); } } @@ -268,7 +264,7 @@ public final class ProviderCreateSession extends ProviderSession< if (credentialResponse != null) { mCallbacks.onFinalResponseReceived(mComponentName, credentialResponse); } else { - Log.i(TAG, "onSaveEntrySelected - no response or error found in pending " + Slog.w(TAG, "onSaveEntrySelected - no response or error found in pending " + "intent response"); invokeCallbackOnInternalInvalidState(); } @@ -284,14 +280,14 @@ public final class ProviderCreateSession extends ProviderSession< private CreateCredentialException maybeGetPendingIntentException( ProviderPendingIntentResponse pendingIntentResponse) { if (pendingIntentResponse == null) { - Log.i(TAG, "pendingIntentResponse is null"); + Slog.w(TAG, "pendingIntentResponse is null"); return new CreateCredentialException(CreateCredentialException.TYPE_NO_CREATE_OPTIONS); } if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) { CreateCredentialException exception = PendingIntentResultHandler .extractCreateCredentialException(pendingIntentResponse.getResultData()); if (exception != null) { - Log.i(TAG, "Pending intent contains provider exception"); + Slog.d(TAG, "Pending intent contains provider exception"); return exception; } } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) { @@ -343,7 +339,7 @@ public final class ProviderCreateSession extends ProviderSession< public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) { if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) { - Log.i(TAG, "Remote entry being dropped as it does not meet the restriction" + Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction" + "checks."); return; } diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java index 8d3d06469944..0c2b5633d501 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java @@ -115,7 +115,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential getRequestSession.mHybridService ); } - Log.i(TAG, "Unable to create provider session"); + Slog.d(TAG, "Unable to create provider session for: " + + providerInfo.getComponentName()); return null; } @@ -146,17 +147,15 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential android.credentials.GetCredentialRequest clientRequest, CredentialProviderInfo info ) { + Slog.d(TAG, "Filtering request options for: " + info.getComponentName()); List<CredentialOption> filteredOptions = new ArrayList<>(); for (CredentialOption option : clientRequest.getCredentialOptions()) { if (providerCapabilities.contains(option.getType()) && isProviderAllowed(option, info.getComponentName()) && checkSystemProviderRequirement(option, info.isSystemProvider())) { - Log.i(TAG, "In createProviderRequest - capability found : " - + option.getType()); + Slog.d(TAG, "Option of type: " + option.getType() + " meets all filtering" + + "conditions"); filteredOptions.add(option); - } else { - Log.i(TAG, "In createProviderRequest - capability not " - + "found, or provider not allowed : " + option.getType()); } } if (!filteredOptions.isEmpty()) { @@ -165,15 +164,14 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential .setCredentialOptions( filteredOptions).build(); } - Log.i(TAG, "In createProviderRequest - returning null"); + Slog.d(TAG, "No options filtered"); return null; } private static boolean isProviderAllowed(CredentialOption option, ComponentName componentName) { if (!option.getAllowedProviders().isEmpty() && !option.getAllowedProviders().contains( componentName)) { - Log.d(TAG, "Provider allow list specified but does not contain this provider: " - + componentName.flattenToString()); + Slog.d(TAG, "Provider allow list specified but does not contain this provider"); return false; } return true; @@ -182,7 +180,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential private static boolean checkSystemProviderRequirement(CredentialOption option, boolean isSystemProvider) { if (option.isSystemProviderRequired() && !isSystemProvider) { - Log.d(TAG, "System provider required, but this service is not a system provider"); + Slog.d(TAG, "System provider required, but this service is not a system provider"); return false; } return true; @@ -210,6 +208,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential /** Called when the provider response has been updated by an external source. */ @Override // Callback from the remote provider public void onProviderResponseSuccess(@Nullable BeginGetCredentialResponse response) { + Slog.d(TAG, "Remote provider responded with a valid response: " + mComponentName); onSetInitialRemoteResponse(response); } @@ -231,7 +230,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential updateStatusAndInvokeCallback(Status.SERVICE_DEAD, /*source=*/ CredentialsSource.REMOTE_PROVIDER); } else { - Slog.i(TAG, "Component names different in onProviderServiceDied - " + Slog.w(TAG, "Component names different in onProviderServiceDied - " + "this should not happen"); } } @@ -244,13 +243,14 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential @Override // Selection call from the request provider protected void onUiEntrySelected(String entryType, String entryKey, ProviderPendingIntentResponse providerPendingIntentResponse) { - Log.i(TAG, "onUiEntrySelected with entryKey: " + entryKey); + Slog.d(TAG, "onUiEntrySelected with entryType: " + entryType + ", and entryKey: " + + entryKey); switch (entryType) { case CREDENTIAL_ENTRY_KEY: CredentialEntry credentialEntry = mProviderResponseDataHandler .getCredentialEntry(entryKey); if (credentialEntry == null) { - Log.i(TAG, "Unexpected credential entry key"); + Slog.w(TAG, "Unexpected credential entry key"); invokeCallbackOnInternalInvalidState(); return; } @@ -259,7 +259,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential case ACTION_ENTRY_KEY: Action actionEntry = mProviderResponseDataHandler.getActionEntry(entryKey); if (actionEntry == null) { - Log.i(TAG, "Unexpected action entry key"); + Slog.w(TAG, "Unexpected action entry key"); invokeCallbackOnInternalInvalidState(); return; } @@ -269,21 +269,21 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential Action authenticationEntry = mProviderResponseDataHandler .getAuthenticationAction(entryKey); if (authenticationEntry == null) { - Log.i(TAG, "Unexpected authenticationEntry key"); + Slog.w(TAG, "Unexpected authenticationEntry key"); invokeCallbackOnInternalInvalidState(); return; } boolean additionalContentReceived = onAuthenticationEntrySelected(providerPendingIntentResponse); if (additionalContentReceived) { - Log.i(TAG, "Additional content received - removing authentication entry"); + Slog.d(TAG, "Additional content received - removing authentication entry"); mProviderResponseDataHandler.removeAuthenticationAction(entryKey); if (!mProviderResponseDataHandler.isEmptyResponse()) { updateStatusAndInvokeCallback(Status.CREDENTIALS_RECEIVED, /*source=*/ CredentialsSource.AUTH_ENTRY); } } else { - Log.i(TAG, "Additional content not received"); + Slog.d(TAG, "Additional content not received from authentication entry"); mProviderResponseDataHandler .updateAuthEntryWithNoCredentialsReceived(entryKey); updateStatusAndInvokeCallback(Status.NO_CREDENTIALS_FROM_AUTH_ENTRY, @@ -294,12 +294,12 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential if (mProviderResponseDataHandler.getRemoteEntry(entryKey) != null) { onRemoteEntrySelected(providerPendingIntentResponse); } else { - Log.i(TAG, "Unexpected remote entry key"); + Slog.d(TAG, "Unexpected remote entry key"); invokeCallbackOnInternalInvalidState(); } break; default: - Log.i(TAG, "Unsupported entry type selected"); + Slog.w(TAG, "Unsupported entry type selected"); invokeCallbackOnInternalInvalidState(); } } @@ -320,26 +320,24 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential @Override // Call from request session to data to be shown on the UI @Nullable protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException { - Log.i(TAG, "In prepareUiData"); if (!ProviderSession.isUiInvokingStatus(getStatus())) { - Log.i(TAG, "In prepareUiData - provider does not want to show UI: " - + mComponentName.flattenToString()); + Slog.d(TAG, "No data for UI from: " + mComponentName.flattenToString()); return null; } if (mProviderResponse != null && !mProviderResponseDataHandler.isEmptyResponse()) { return mProviderResponseDataHandler.toGetCredentialProviderData(); } - Log.i(TAG, "In prepareUiData response null"); + Slog.d(TAG, "In prepareUiData response null"); return null; } - private Intent setUpFillInIntent(@NonNull String id) { + private Intent setUpFillInIntentWithFinalRequest(@NonNull String id) { // TODO: Determine if we should skip this entry if entry id is not set, or is set // but does not resolve to a valid option. For now, not skipping it because // it may be possible that the provider adds their own extras and expects to receive // those and complete the flow. if (mBeginGetOptionToCredentialOptionMap.get(id) == null) { - Log.i(TAG, "Id from Credential Entry does not resolve to a valid option"); + Slog.w(TAG, "Id from Credential Entry does not resolve to a valid option"); return new Intent(); } return new Intent().putExtra(CredentialProviderService.EXTRA_GET_CREDENTIAL_REQUEST, @@ -382,7 +380,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential getCredentialResponse); return; } - Log.i(TAG, "Pending intent response contains no credential, or error"); + Slog.d(TAG, "Pending intent response contains no credential, or error " + + "for a credential entry"); invokeCallbackOnInternalInvalidState(); } @@ -390,14 +389,12 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential private GetCredentialException maybeGetPendingIntentException( ProviderPendingIntentResponse pendingIntentResponse) { if (pendingIntentResponse == null) { - Log.i(TAG, "pendingIntentResponse is null"); return null; } if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) { GetCredentialException exception = PendingIntentResultHandler .extractGetCredentialException(pendingIntentResponse.getResultData()); if (exception != null) { - Log.i(TAG, "Pending intent contains provider exception"); return exception; } } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) { @@ -463,7 +460,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential /** Returns true if either an exception or a response is found. */ private void onActionEntrySelected(ProviderPendingIntentResponse providerPendingIntentResponse) { - Log.i(TAG, "onActionEntrySelected"); + Slog.d(TAG, "onActionEntrySelected"); onCredentialEntrySelected(providerPendingIntentResponse); } @@ -559,7 +556,8 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential String id = generateUniqueId(); Entry entry = new Entry(CREDENTIAL_ENTRY_KEY, id, credentialEntry.getSlice(), - setUpFillInIntent(credentialEntry.getBeginGetCredentialOptionId())); + setUpFillInIntentWithFinalRequest(credentialEntry + .getBeginGetCredentialOptionId())); mUiCredentialEntries.put(id, new Pair<>(credentialEntry, entry)); mCredentialEntryTypes.add(credentialEntry.getType()); } @@ -574,9 +572,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential public void addAuthenticationAction(Action authenticationAction, @AuthenticationEntry.Status int status) { - Log.i(TAG, "In addAuthenticationAction"); String id = generateUniqueId(); - Log.i(TAG, "In addAuthenticationAction, id : " + id); AuthenticationEntry entry = new AuthenticationEntry( AUTHENTICATION_ACTION_ENTRY_KEY, id, authenticationAction.getSlice(), @@ -591,7 +587,7 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential public void setRemoteEntry(@Nullable RemoteEntry remoteEntry) { if (!enforceRemoteEntryRestrictions(mExpectedRemoteEntryProviderService)) { - Log.i(TAG, "Remote entry being dropped as it does not meet the restriction" + Slog.w(TAG, "Remote entry being dropped as it does not meet the restriction" + " checks."); return; } @@ -715,7 +711,6 @@ public final class ProviderGetSession extends ProviderSession<BeginGetCredential == AuthenticationEntry.STATUS_UNLOCKED_BUT_EMPTY_MOST_RECENT) .findFirst(); if (previousMostRecentAuthEntry.isEmpty()) { - Log.i(TAG, "In updatePreviousMostRecentAuthEntry - previous entry not found"); return; } String id = previousMostRecentAuthEntry.get().getKey(); diff --git a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java index 24292ef2cdab..c10f5640c466 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderRegistryGetSession.java @@ -33,7 +33,7 @@ import android.os.ICancellationSignal; import android.service.credentials.CallingAppInfo; import android.service.credentials.CredentialEntry; import android.service.credentials.CredentialProviderService; -import android.telecom.Log; +import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -145,14 +145,12 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption private List<Entry> prepareUiCredentialEntries( @NonNull List<CredentialEntry> credentialEntries) { - Log.i(TAG, "in prepareUiProviderDataWithCredentials"); List<Entry> credentialUiEntries = new ArrayList<>(); // Populate the credential entries for (CredentialEntry credentialEntry : credentialEntries) { String entryId = generateUniqueId(); mUiCredentialEntries.put(entryId, credentialEntry); - Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId); credentialUiEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId, credentialEntry.getSlice(), setUpFillInIntent())); @@ -172,15 +170,13 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption @Override protected ProviderData prepareUiData() { - Log.i(TAG, "In prepareUiData"); if (!ProviderSession.isUiInvokingStatus(getStatus())) { - Log.i(TAG, "In prepareUiData - provider does not want to show UI: " - + mComponentName.flattenToString()); + Slog.d(TAG, "No date for UI coming from: " + mComponentName.flattenToString()); return null; } if (mProviderResponse == null) { - Log.i(TAG, "In prepareUiData response null"); - throw new IllegalStateException("Response must be in completion mode"); + Slog.w(TAG, "In prepareUiData but response is null. This is strange."); + return null; } return new GetCredentialProviderData.Builder( mComponentName.flattenToString()).setActionChips(null) @@ -200,13 +196,13 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption case CREDENTIAL_ENTRY_KEY: CredentialEntry credentialEntry = mUiCredentialEntries.get(entryKey); if (credentialEntry == null) { - Log.i(TAG, "Unexpected credential entry key"); + Slog.w(TAG, "Unexpected credential entry key"); return; } onCredentialEntrySelected(credentialEntry, providerPendingIntentResponse); break; default: - Log.i(TAG, "Unsupported entry type selected"); + Slog.w(TAG, "Unsupported entry type selected"); } } @@ -233,10 +229,8 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption } return; } - - Log.i(TAG, "Pending intent response contains no credential, or error"); } - Log.i(TAG, "CredentialEntry does not have a credential or a pending intent result"); + Slog.w(TAG, "CredentialEntry does not have a credential or a pending intent result"); } @Override @@ -279,14 +273,13 @@ public class ProviderRegistryGetSession extends ProviderSession<CredentialOption protected GetCredentialException maybeGetPendingIntentException( ProviderPendingIntentResponse pendingIntentResponse) { if (pendingIntentResponse == null) { - android.util.Log.i(TAG, "pendingIntentResponse is null"); return null; } if (PendingIntentResultHandler.isValidResponse(pendingIntentResponse)) { GetCredentialException exception = PendingIntentResultHandler .extractGetCredentialException(pendingIntentResponse.getResultData()); if (exception != null) { - android.util.Log.i(TAG, "Pending intent contains provider exception"); + Slog.d(TAG, "Pending intent contains provider exception"); return exception; } } else if (PendingIntentResultHandler.isCancelledResponse(pendingIntentResponse)) { diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java index d165756b3811..d02a8c1ee510 100644 --- a/services/credentials/java/com/android/server/credentials/ProviderSession.java +++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java @@ -29,7 +29,6 @@ import android.credentials.ui.ProviderData; import android.credentials.ui.ProviderPendingIntentResponse; import android.os.ICancellationSignal; import android.os.RemoteException; -import android.util.Log; import android.util.Slog; import com.android.server.credentials.metrics.ProviderSessionMetric; @@ -253,7 +252,7 @@ public abstract class ProviderSession<T, R> @Nullable ComponentName expectedRemoteEntryProviderService) { // Check if the service is the one set by the OEM. If not silently reject this entry if (!mComponentName.equals(expectedRemoteEntryProviderService)) { - Log.i(TAG, "Remote entry being dropped as it is not from the service " + Slog.w(TAG, "Remote entry being dropped as it is not from the service " + "configured by the OEM."); return false; } @@ -270,15 +269,12 @@ public abstract class ProviderSession<T, R> return true; } } catch (SecurityException e) { - Log.i(TAG, "Error getting info for " - + mComponentName.flattenToString() + ": " + e.getMessage()); + Slog.e(TAG, "Error getting info for " + mComponentName.flattenToString(), e); return false; } catch (PackageManager.NameNotFoundException e) { - Log.i(TAG, "Error getting info for " - + mComponentName.flattenToString() + ": " + e.getMessage()); + Slog.i(TAG, "Error getting info for " + mComponentName.flattenToString(), e); return false; } - Log.i(TAG, "In enforceRemoteEntryRestrictions - remote entry checks fail"); return false; } 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 f111a9541303..926c7e400144 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -27,6 +27,7 @@ import static android.content.pm.UserProperties.INHERIT_DEVICE_POLICY_FROM_PAREN import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.BroadcastOptions; import android.app.admin.DevicePolicyIdentifiers; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyState; @@ -353,6 +354,7 @@ final class DevicePolicyEngine { policyDefinition, userId); } + sendDevicePolicyChangedToSystem(userId); } /** @@ -478,6 +480,8 @@ final class DevicePolicyEngine { enforcingAdmin, policyDefinition, UserHandle.USER_ALL); + + sendDevicePolicyChangedToSystem(UserHandle.USER_ALL); } /** @@ -699,7 +703,7 @@ final class DevicePolicyEngine { if (policyDefinition.isGlobalOnlyPolicy()) { throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a global only" - + "policy."); + + " policy."); } if (!mLocalPolicies.contains(userId)) { @@ -724,7 +728,7 @@ final class DevicePolicyEngine { private <V> PolicyState<V> getGlobalPolicyStateLocked(PolicyDefinition<V> policyDefinition) { if (policyDefinition.isLocalOnlyPolicy()) { throw new IllegalArgumentException(policyDefinition.getPolicyKey() + " is a local only" - + "policy."); + + " policy."); } if (!mGlobalPolicies.containsKey(policyDefinition.getPolicyKey())) { @@ -761,6 +765,20 @@ final class DevicePolicyEngine { policyValue == null ? null : policyValue.getValue(), mContext, userId); } + private void sendDevicePolicyChangedToSystem(int userId) { + Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); + intent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + Bundle options = new BroadcastOptions() + .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT) + .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) + .toBundle(); + Binder.withCleanCallingIdentity(() -> mContext.sendBroadcastAsUser( + intent, + new UserHandle(userId), + /* receiverPermissions= */ null, + options)); + } + private <V> void sendPolicyResultToAdmin( EnforcingAdmin admin, PolicyDefinition<V> policyDefinition, int result, int userId) { Intent intent = new Intent(PolicyUpdateReceiver.ACTION_DEVICE_POLICY_SET_RESULT); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 94e6e732dd76..29f9a306880f 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -19,6 +19,7 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.BIND_DEVICE_ADMIN; import static android.Manifest.permission.LOCK_DEVICE; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; +import static android.Manifest.permission.MANAGE_DEFAULT_APPLICATIONS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL; @@ -59,6 +60,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_PROFILE_INTERACTI import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESET_PASSWORD; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS; +import static android.Manifest.permission.MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SAFE_BOOT; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CAPTURE; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SCREEN_CONTENT; @@ -441,7 +443,6 @@ import android.util.AtomicFile; import android.util.DebugUtils; import android.util.IndentingPrintWriter; import android.util.IntArray; -import android.util.Log; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -7767,12 +7768,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Explicit behaviour if (factoryReset) { // TODO(b/254031494) Replace with new factory reset permission checks - boolean hasPermission = isDeviceOwnerUserId(userId) - || (isOrganizationOwnedDeviceWithManagedProfile() - && calledOnParentInstance); - Preconditions.checkState(hasPermission, - "Admin %s does not have permission to factory reset the device.", - userId); + if (!isPermissionCheckFlagEnabled()) { + boolean hasPermission = isDeviceOwnerUserId(userId) + || (isOrganizationOwnedDeviceWithManagedProfile() + && calledOnParentInstance); + Preconditions.checkCallAuthorization(hasPermission, + "Admin %s does not have permission to factory reset the device.", + userId); + } wipeDevice = true; } else { Preconditions.checkCallAuthorization(!isSystemUser, @@ -13131,19 +13134,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } int userId = caller.getUserId(); - if (!UserRestrictionsUtils.isValidRestriction(key)) { - return; - } checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION); if (isPolicyEngineForFinanceFlagEnabled()) { - int affectedUserId = parent ? getProfileParentId(userId) : userId; - EnforcingAdmin admin = enforcePermissionForUserRestriction( - who, - key, - caller.getPackageName(), - affectedUserId); - if (mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) { + if (!isDeviceOwner(caller) && !isProfileOwner(caller)) { + if (!mInjector.isChangeEnabled(ENABLE_COEXISTENCE_CHANGE, callerPackage, userId)) { + throw new IllegalStateException("Calling package is not targeting Android U."); + } + if (!UserRestrictionsUtils.isValidRestriction(key)) { + throw new IllegalArgumentException("Invalid restriction key: " + key); + } + int affectedUserId = parent ? getProfileParentId(userId) : userId; + EnforcingAdmin admin = enforcePermissionForUserRestriction( + who, + key, + caller.getPackageName(), + affectedUserId); PolicyDefinition<Boolean> policyDefinition = PolicyDefinition.getPolicyDefinitionForUserRestriction(key); if (enabledFromThisOwner) { @@ -13155,7 +13161,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { setGlobalUserRestrictionInternal(admin, key, /* enabled= */ false); } if (!policyDefinition.isGlobalOnlyPolicy()) { - setLocalUserRestrictionInternal(admin, key, /* enabled= */ false, userId); + setLocalUserRestrictionInternal(admin, key, /* enabled= */ false, + userId); int parentUserId = getProfileParentId(userId); if (parentUserId != userId) { @@ -13165,49 +13172,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } else { + if (!UserRestrictionsUtils.isValidRestriction(key)) { + return; + } + Objects.requireNonNull(who, "ComponentName is null"); + EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage); + checkAdminCanSetRestriction(caller, parent, key); setBackwardCompatibleUserRestriction( caller, admin, key, enabledFromThisOwner, parent); } } else { - Objects.requireNonNull(who, "ComponentName is null"); - if (parent) { - Preconditions.checkCallAuthorization( - isProfileOwnerOfOrganizationOwnedDevice(caller)); - } else { - Preconditions.checkCallAuthorization( - isDeviceOwner(caller) || isProfileOwner(caller)); - } - synchronized (getLockObject()) { - if (isDefaultDeviceOwner(caller)) { - if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) { - throw new SecurityException("Device owner cannot set user restriction " - + key); - } - Preconditions.checkArgument(!parent, - "Cannot use the parent instance in Device Owner mode"); - } else if (isFinancedDeviceOwner(caller)) { - if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) { - throw new SecurityException("Cannot set user restriction " + key - + " when managing a financed device"); - } - Preconditions.checkArgument(!parent, - "Cannot use the parent instance in Financed Device Owner" - + " mode"); - } else { - boolean profileOwnerCanChangeOnItself = !parent - && UserRestrictionsUtils.canProfileOwnerChange( - key, userId == getMainUserId()); - boolean orgOwnedProfileOwnerCanChangeGlobally = parent - && isProfileOwnerOfOrganizationOwnedDevice(caller) - && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange( - key); - - if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) { - throw new SecurityException("Profile owner cannot set user restriction " - + key); - } - } + if (!UserRestrictionsUtils.isValidRestriction(key)) { + return; } + Objects.requireNonNull(who, "ComponentName is null"); + checkAdminCanSetRestriction(caller, parent, key); synchronized (getLockObject()) { final ActiveAdmin activeAdmin = getParentOfAdminIfRequired( getProfileOwnerOrDeviceOwnerLocked(userId), parent); @@ -13224,6 +13203,46 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { logUserRestrictionCall(key, enabledFromThisOwner, parent, caller); } + private void checkAdminCanSetRestriction(CallerIdentity caller, boolean parent, String key) { + if (parent) { + Preconditions.checkCallAuthorization( + isProfileOwnerOfOrganizationOwnedDevice(caller)); + } else { + Preconditions.checkCallAuthorization( + isDeviceOwner(caller) || isProfileOwner(caller)); + } + synchronized (getLockObject()) { + if (isDefaultDeviceOwner(caller)) { + if (!UserRestrictionsUtils.canDeviceOwnerChange(key)) { + throw new SecurityException("Device owner cannot set user restriction " + + key); + } + Preconditions.checkArgument(!parent, + "Cannot use the parent instance in Device Owner mode"); + } else if (isFinancedDeviceOwner(caller)) { + if (!UserRestrictionsUtils.canFinancedDeviceOwnerChange(key)) { + throw new SecurityException("Cannot set user restriction " + key + + " when managing a financed device"); + } + Preconditions.checkArgument(!parent, + "Cannot use the parent instance in Financed Device Owner" + + " mode"); + } else { + boolean profileOwnerCanChangeOnItself = !parent + && UserRestrictionsUtils.canProfileOwnerChange( + key, caller.getUserId() == getMainUserId()); + boolean orgOwnedProfileOwnerCanChangeGlobally = parent + && isProfileOwnerOfOrganizationOwnedDevice(caller) + && UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange( + key); + + if (!profileOwnerCanChangeOnItself && !orgOwnedProfileOwnerCanChangeGlobally) { + throw new SecurityException("Profile owner cannot set user restriction " + + key); + } + } + } + } private void setBackwardCompatibleUserRestriction( CallerIdentity caller, EnforcingAdmin admin, String key, boolean enabled, boolean parent) { @@ -13252,20 +13271,22 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setUserRestrictionGlobally(String callerPackage, String key) { final CallerIdentity caller = getCallerIdentity(callerPackage); - if (!UserRestrictionsUtils.isValidRestriction(key)) { - return; - } checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_USER_RESTRICTION); if (!isPolicyEngineForFinanceFlagEnabled()) { throw new IllegalStateException("Feature flag is not enabled."); } - + if (isDeviceOwner(caller) || isProfileOwner(caller)) { + throw new IllegalStateException("Admins are not allowed to call this API."); + } if (!mInjector.isChangeEnabled( ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) { throw new IllegalStateException("Calling package is not targeting Android U."); } + if (!UserRestrictionsUtils.isValidRestriction(key)) { + throw new IllegalArgumentException("Invalid restriction key: " + key); + } EnforcingAdmin admin = enforcePermissionForUserRestriction( /* who= */ null, @@ -13416,14 +13437,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int targetUserId = parent ? getProfileParentId(caller.getUserId()) : caller.getUserId(); EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackage); - Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId); - // Add global restrictions set by the admin as well if admin is not targeting Android U. - if (!mInjector.isChangeEnabled( - ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) { + if (isDeviceOwner(caller) || isProfileOwner(caller)) { + Objects.requireNonNull(who, "ComponentName is null"); + Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) + || isFinancedDeviceOwner(caller) + || isProfileOwner(caller) + || (parent && isProfileOwnerOfOrganizationOwnedDevice(caller))); + + Bundle restrictions = getUserRestrictionsFromPolicyEngine(admin, targetUserId); + // Add global restrictions set by the admin as well. restrictions.putAll( getUserRestrictionsFromPolicyEngine(admin, UserHandle.USER_ALL)); + return restrictions; + } else { + if (!mInjector.isChangeEnabled( + ENABLE_COEXISTENCE_CHANGE, callerPackage, caller.getUserId())) { + throw new IllegalStateException("Calling package is not targeting Android U."); + } + return getUserRestrictionsFromPolicyEngine(admin, targetUserId); } - return restrictions; } else { Objects.requireNonNull(who, "ComponentName is null"); Preconditions.checkCallAuthorization(isDefaultDeviceOwner(caller) @@ -13439,164 +13471,162 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } // Map of user restriction to permission. - private static final HashMap<String, String> USER_RESTRICTION_PERMISSIONS = new HashMap<>(); + private static final HashMap<String, String[]> USER_RESTRICTION_PERMISSIONS = new HashMap<>(); { USER_RESTRICTION_PERMISSIONS.put( - UserManager.ENSURE_VERIFY_APPS, MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES); + UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, new String[]{MANAGE_DEVICE_POLICY_PROFILES}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_WIFI_TETHERING, MANAGE_DEVICE_POLICY_WIFI); + UserManager.DISALLOW_ADD_CLONE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_WIFI_DIRECT, MANAGE_DEVICE_POLICY_WIFI); + UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_USERS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_USER_SWITCH, MANAGE_DEVICE_POLICY_USERS); + UserManager.DISALLOW_ADD_WIFI_CONFIG, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_USB_FILE_TRANSFER, MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER); + UserManager.DISALLOW_ADJUST_VOLUME, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_UNMUTE_MICROPHONE, MANAGE_DEVICE_POLICY_MICROPHONE); + UserManager.DISALLOW_AIRPLANE_MODE, new String[]{MANAGE_DEVICE_POLICY_AIRPLANE_MODE}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_UNMUTE_DEVICE, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT); + UserManager.DISALLOW_AMBIENT_DISPLAY, new String[]{MANAGE_DEVICE_POLICY_DISPLAY}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_UNINSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL); + UserManager.DISALLOW_APPS_CONTROL, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_UNIFIED_PASSWORD, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS); + UserManager.DISALLOW_AUTOFILL, new String[]{MANAGE_DEVICE_POLICY_AUTOFILL}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS); + UserManager.DISALLOW_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SMS, MANAGE_DEVICE_POLICY_SMS); + UserManager.DISALLOW_BLUETOOTH_SHARING, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, MANAGE_DEVICE_POLICY_WIFI); + UserManager.DISALLOW_CAMERA, new String[]{MANAGE_DEVICE_POLICY_CAMERA}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SHARE_LOCATION, MANAGE_DEVICE_POLICY_LOCATION); + UserManager.DISALLOW_CAMERA_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_CAMERA_TOGGLE}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION); + UserManager.DISALLOW_CELLULAR_2G, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SET_WALLPAPER, MANAGE_DEVICE_POLICY_WALLPAPER); + UserManager.DISALLOW_CHANGE_WIFI_STATE, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SET_USER_ICON, MANAGE_DEVICE_POLICY_USERS); + UserManager.DISALLOW_CONFIG_BLUETOOTH, new String[]{MANAGE_DEVICE_POLICY_BLUETOOTH}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_SAFE_BOOT, MANAGE_DEVICE_POLICY_SAFE_BOOT); + UserManager.DISALLOW_CONFIG_BRIGHTNESS, new String[]{MANAGE_DEVICE_POLICY_DISPLAY}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_RUN_IN_BACKGROUND, MANAGE_DEVICE_POLICY_SAFE_BOOT); + UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_REMOVE_USER, MANAGE_DEVICE_POLICY_USERS); + UserManager.DISALLOW_CONFIG_CREDENTIALS, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_PRINTING, MANAGE_DEVICE_POLICY_PRINTING); + UserManager.DISALLOW_CONFIG_DATE_TIME, new String[]{MANAGE_DEVICE_POLICY_TIME}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_OUTGOING_CALLS, MANAGE_DEVICE_POLICY_CALLS); + UserManager.DISALLOW_CONFIG_DEFAULT_APPS, new String[]{MANAGE_DEFAULT_APPLICATIONS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_OUTGOING_BEAM, MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION); + UserManager.DISALLOW_CONFIG_LOCALE, new String[]{MANAGE_DEVICE_POLICY_LOCALE}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_NETWORK_RESET, MANAGE_DEVICE_POLICY_MOBILE_NETWORK); + UserManager.DISALLOW_CONFIG_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA); + UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_MODIFY_ACCOUNTS, MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT); + UserManager.DISALLOW_CONFIG_PRIVATE_DNS, new String[]{MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_MICROPHONE_TOGGLE, MANAGE_DEVICE_POLICY_MICROPHONE); + UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, new String[]{MANAGE_DEVICE_POLICY_DISPLAY}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, - MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES); + UserManager.DISALLOW_CONFIG_TETHERING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, - MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES); + UserManager.DISALLOW_CONFIG_VPN, new String[]{MANAGE_DEVICE_POLICY_VPN}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_INSTALL_APPS, MANAGE_DEVICE_POLICY_APPS_CONTROL); + UserManager.DISALLOW_CONFIG_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_FUN, MANAGE_DEVICE_POLICY_FUN); + UserManager.DISALLOW_CONTENT_CAPTURE, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_FACTORY_RESET, MANAGE_DEVICE_POLICY_FACTORY_RESET); + UserManager.DISALLOW_CONTENT_SUGGESTIONS, new String[]{MANAGE_DEVICE_POLICY_SCREEN_CONTENT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_DEBUGGING_FEATURES, MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES); + UserManager.DISALLOW_CREATE_WINDOWS, new String[]{MANAGE_DEVICE_POLICY_WINDOWS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_DATA_ROAMING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK); + UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE, - MANAGE_DEVICE_POLICY_PROFILE_INTERACTION); + UserManager.DISALLOW_DATA_ROAMING, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CREATE_WINDOWS, MANAGE_DEVICE_POLICY_WINDOWS); + UserManager.DISALLOW_DEBUGGING_FEATURES, new String[]{MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONTENT_SUGGESTIONS, MANAGE_DEVICE_POLICY_SCREEN_CONTENT); + UserManager.DISALLOW_FACTORY_RESET, new String[]{MANAGE_DEVICE_POLICY_FACTORY_RESET}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONTENT_CAPTURE, MANAGE_DEVICE_POLICY_SCREEN_CONTENT); + UserManager.DISALLOW_FUN, new String[]{MANAGE_DEVICE_POLICY_FUN}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_WIFI, MANAGE_DEVICE_POLICY_WIFI); + UserManager.DISALLOW_INSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_VPN, MANAGE_DEVICE_POLICY_VPN); + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_TETHERING, MANAGE_DEVICE_POLICY_MOBILE_NETWORK); + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_SCREEN_TIMEOUT, MANAGE_DEVICE_POLICY_DISPLAY); + UserManager.DISALLOW_MICROPHONE_TOGGLE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_PRIVATE_DNS, MANAGE_DEVICE_POLICY_RESTRICT_PRIVATE_DNS); + UserManager.DISALLOW_MODIFY_ACCOUNTS, new String[]{MANAGE_DEVICE_POLICY_ACCOUNT_MANAGEMENT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK); + UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, new String[]{MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_LOCATION, MANAGE_DEVICE_POLICY_LOCATION); + UserManager.DISALLOW_NETWORK_RESET, new String[]{MANAGE_DEVICE_POLICY_MOBILE_NETWORK}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_LOCALE, MANAGE_DEVICE_POLICY_LOCALE); + UserManager.DISALLOW_OUTGOING_BEAM, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_DATE_TIME, MANAGE_DEVICE_POLICY_TIME); + UserManager.DISALLOW_OUTGOING_CALLS, new String[]{MANAGE_DEVICE_POLICY_CALLS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_CREDENTIALS, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS); + UserManager.DISALLOW_PRINTING, new String[]{MANAGE_DEVICE_POLICY_PRINTING}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, MANAGE_DEVICE_POLICY_MOBILE_NETWORK); + UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_USERS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_BRIGHTNESS, MANAGE_DEVICE_POLICY_DISPLAY); + UserManager.DISALLOW_RUN_IN_BACKGROUND, new String[]{MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH); + UserManager.DISALLOW_SAFE_BOOT, new String[]{MANAGE_DEVICE_POLICY_SAFE_BOOT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CHANGE_WIFI_STATE, MANAGE_DEVICE_POLICY_WIFI); + UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_USERS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CAMERA_TOGGLE, MANAGE_DEVICE_POLICY_CAMERA); + UserManager.DISALLOW_SET_WALLPAPER, new String[]{MANAGE_DEVICE_POLICY_WALLPAPER}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CAMERA, MANAGE_DEVICE_POLICY_CAMERA); + UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILE_INTERACTION}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_BLUETOOTH_SHARING, MANAGE_DEVICE_POLICY_BLUETOOTH); + UserManager.DISALLOW_SHARE_LOCATION, new String[]{MANAGE_DEVICE_POLICY_LOCATION}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_BLUETOOTH, MANAGE_DEVICE_POLICY_BLUETOOTH); + UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_BIOMETRIC, MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS); + UserManager.DISALLOW_SMS, new String[]{MANAGE_DEVICE_POLICY_SMS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_AUTOFILL, MANAGE_DEVICE_POLICY_AUTOFILL); + UserManager.DISALLOW_SYSTEM_ERROR_DIALOGS, new String[]{MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_APPS_CONTROL, MANAGE_DEVICE_POLICY_APPS_CONTROL); + UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, new String[]{MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_AMBIENT_DISPLAY, MANAGE_DEVICE_POLICY_DISPLAY); + UserManager.DISALLOW_UNIFIED_PASSWORD, new String[]{MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_AIRPLANE_MODE, MANAGE_DEVICE_POLICY_AIRPLANE_MODE); + UserManager.DISALLOW_UNINSTALL_APPS, new String[]{MANAGE_DEVICE_POLICY_APPS_CONTROL}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_ADJUST_VOLUME, MANAGE_DEVICE_POLICY_AUDIO_OUTPUT); + UserManager.DISALLOW_UNMUTE_DEVICE, new String[]{MANAGE_DEVICE_POLICY_AUDIO_OUTPUT}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_ADD_WIFI_CONFIG, MANAGE_DEVICE_POLICY_WIFI); + UserManager.DISALLOW_UNMUTE_MICROPHONE, new String[]{MANAGE_DEVICE_POLICY_MICROPHONE}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_ADD_USER, MANAGE_DEVICE_POLICY_USERS); + UserManager.DISALLOW_USB_FILE_TRANSFER, new String[]{MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_ADD_CLONE_PROFILE, MANAGE_DEVICE_POLICY_PROFILES); + UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_USERS}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, MANAGE_DEVICE_POLICY_PROFILES); + UserManager.DISALLOW_WIFI_DIRECT, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CELLULAR_2G, MANAGE_DEVICE_POLICY_MOBILE_NETWORK); + UserManager.DISALLOW_WIFI_TETHERING, new String[]{MANAGE_DEVICE_POLICY_WIFI}); USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_ULTRA_WIDEBAND_RADIO, - MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION); + UserManager.ENSURE_VERIFY_APPS, new String[]{MANAGE_DEVICE_POLICY_INSTALL_UNKNOWN_SOURCES}); // Restrictions not allowed to be set by admins. USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_RECORD_AUDIO, null); USER_RESTRICTION_PERMISSIONS.put( UserManager.DISALLOW_WALLPAPER, null); - USER_RESTRICTION_PERMISSIONS.put( - UserManager.DISALLOW_CONFIG_DEFAULT_APPS, null); } private EnforcingAdmin enforcePermissionForUserRestriction(ComponentName who, String userRestriction, String callerPackageName, int userId) { - String permission = USER_RESTRICTION_PERMISSIONS.get(userRestriction); - if (permission != null) { - return enforcePermissionAndGetEnforcingAdmin(who, permission, callerPackageName, - userId); - } + String[] permissions = USER_RESTRICTION_PERMISSIONS.get(userRestriction); + if (permissions.length > 0) { + try { + return enforcePermissionsAndGetEnforcingAdmin(who, permissions, callerPackageName, + userId); + } catch (SecurityException e) { + throw new SecurityException("Caller does not hold the required permission for this " + + "user restriction: " + userRestriction + ".\n" + e.getMessage()); + } + } throw new SecurityException("Admins are not permitted to set User Restriction: " + userRestriction); } @@ -14017,9 +14047,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(who, callerPackage); if (isPolicyEngineForFinanceFlagEnabled()) { - EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( + EnforcingAdmin enforcingAdmin = enforcePermissionsAndGetEnforcingAdmin( who, - MANAGE_DEVICE_POLICY_APPS_CONTROL, + new String[]{ + MANAGE_DEVICE_POLICY_APPS_CONTROL, + MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL + }, caller.getPackageName(), caller.getUserId()); mDevicePolicyEngine.setLocalPolicy( @@ -16626,10 +16659,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { SENSOR_PERMISSIONS.add(Manifest.permission.BACKGROUND_CAMERA); SENSOR_PERMISSIONS.add(Manifest.permission.RECORD_BACKGROUND_AUDIO); SENSOR_PERMISSIONS.add(Manifest.permission.BODY_SENSORS_BACKGROUND); - SENSOR_PERMISSIONS.add( - Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE); - SENSOR_PERMISSIONS.add( - Manifest.permission.BODY_SENSORS_WRIST_TEMPERATURE_BACKGROUND); } private boolean canGrantPermission(CallerIdentity caller, String permission, @@ -22422,6 +22451,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { }); } + // Permission that will need to be created in V. + private static final String MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL = + "manage_device_policy_block_uninstall"; + private static final String MANAGE_DEVICE_POLICY_CAMERA_TOGGLE = + "manage_device_policy_camera_toggle"; + private static final String MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE = + "manage_device_policy_microphone_toggle"; + // DPC types private static final int DEFAULT_DEVICE_OWNER = 0; private static final int FINANCED_DEVICE_OWNER = 1; @@ -22645,7 +22682,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { { DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS, DELEGATION_PERMISSION_GRANT); DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APP_RESTRICTIONS, DELEGATION_APP_RESTRICTIONS); - DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_APPS_CONTROL, DELEGATION_BLOCK_UNINSTALL); + DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL, DELEGATION_BLOCK_UNINSTALL); DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_SECURITY_LOGGING, DELEGATION_SECURITY_LOGGING); DELEGATE_SCOPES.put(MANAGE_DEVICE_POLICY_PACKAGE_STATE, DELEGATION_PACKAGE_ACCESS); } @@ -22724,6 +22761,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_AUTOFILL, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_BLOCK_UNINSTALL, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_CAMERA_TOGGLE, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_COMMON_CRITERIA_MODE, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_DEBUGGING_FEATURES, @@ -22740,6 +22781,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); + CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE, + MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY, MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL); CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_PROFILES, @@ -22770,13 +22813,34 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { /** * Checks if the calling process has been granted permission to apply a device policy on a + * specific user. Only one permission provided in the list needs to be granted to pass this + * check. + * The given permissions will be checked along with their associated cross-user permissions if + * they exists and the target user is different to the calling user. + * Returns an {@link EnforcingAdmin} for the caller. + * + * @param admin the component name of the admin. + * @param callerPackageName The package name of the calling application. + * @param permissions an array of permission names to be checked. + * @param targetUserId The userId of the user which the caller needs permission to act on. + * @throws SecurityException if the caller has not been granted the given permission, + * the associated cross-user permission if the caller's user is different to the target user. + */ + private EnforcingAdmin enforcePermissionsAndGetEnforcingAdmin(@Nullable ComponentName admin, + String[] permissions, String callerPackageName, int targetUserId) { + enforcePermissions(permissions, callerPackageName, targetUserId); + return getEnforcingAdminForCaller(admin, callerPackageName); + } + + /** + * Checks if the calling process has been granted permission to apply a device policy on a * specific user. * The given permission will be checked along with its associated cross-user permission if it * exists and the target user is different to the calling user. * Returns an {@link EnforcingAdmin} for the caller. * * @param admin the component name of the admin. - * @param callerPackageName The package name of the calling application. + * @param callerPackageName The package name of the calling application. * @param permission The name of the permission being checked. * @param targetUserId The userId of the user which the caller needs permission to act on. * @throws SecurityException if the caller has not been granted the given permission, @@ -22834,6 +22898,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { new HashMap<>(); /** + * Checks if the calling process has been granted permission to apply a device policy. + * + * @param callerPackageName The package name of the calling application. + * @param permission The name of the permission being checked. + * @throws SecurityException if the caller has not been granted the given permission, + * the associated cross-user permission if the caller's user is different to the target user. + */ + private void enforcePermission(String permission, String callerPackageName) + throws SecurityException { + if (!hasPermission(permission, callerPackageName)) { + throw new SecurityException("Caller does not have the required permissions for " + + "this user. Permission required: " + + permission + + "."); + } + } + + + /** * Checks if the calling process has been granted permission to apply a device policy on a * specific user. * The given permission will be checked along with its associated cross-user permission if it @@ -22847,17 +22930,40 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { */ private void enforcePermission(String permission, String callerPackageName, int targetUserId) throws SecurityException { - if (!hasPermission(permission, callerPackageName, targetUserId)) { - // TODO(b/276920002): Split the error messages so that the cross-user permission - // is only mentioned when it is needed. + enforcePermission(permission, callerPackageName); + if (targetUserId != getCallerIdentity(callerPackageName).getUserId()) { + enforcePermission(CROSS_USER_PERMISSIONS.get(permission), callerPackageName); + } + } + + /** + * Checks if the calling process has been granted permission to apply a device policy on a + * specific user. Only one of the given permissions will be required to be held to pass this + * check. + * The given permissions will be checked along with their associated cross-user permissions if + * they exist and the target user is different to the calling user. + * + * @param permissions An array of the names of the permissions being checked. + * @param callerPackageName The package name of the calling application. + * @param targetUserId The userId of the user which the caller needs permission to act on. + * @throws SecurityException if the caller has not been granted the given permission, + * the associated cross-user permission if the caller's user is different to the target user. + */ + private void enforcePermissions(String[] permissions, String callerPackageName, + int targetUserId) throws SecurityException { + String heldPermission = ""; + for (String permission : permissions) { + if (hasPermission(permission, callerPackageName)) { + heldPermission = permission; + break; + } + } + if (heldPermission.isEmpty()) { throw new SecurityException("Caller does not have the required permissions for " - + "this user. Permissions required: {" - + permission - + ", " - + CROSS_USER_PERMISSIONS.get(permission) - + "(if calling cross-user)" - + "}"); + + "this user. One of the following permission required: " + + Arrays.toString(permissions)); } + enforcePermission(heldPermission, callerPackageName, targetUserId); } /** @@ -22867,25 +22973,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * exists and the target user is different to the calling user. * * @param callerPackageName The package name of the calling application. + * @param adminPolicy The admin policy that should grant holders permission. * @param permission The name of the permission being checked. * @param targetUserId The userId of the user which the caller needs permission to act on. * @throws SecurityException if the caller has not been granted the given permission, * the associated cross-user permission if the caller's user is different to the target user. */ private void enforcePermission(String permission, int adminPolicy, - String callerPackageName, int targetUserId) - throws SecurityException { - if (!hasPermissionOrAdminPolicy(permission, callerPackageName, adminPolicy, targetUserId)) { - // TODO(b/276920002): Split the error messages so that the cross-user permission - // is only mentioned when it is needed. - throw new SecurityException("Caller does not have the required permissions for " - + "this user. Permissions required: {" - + permission - + ", " - + CROSS_USER_PERMISSIONS.get(permission) - + "(if calling cross-user)" - + "}"); + String callerPackageName, int targetUserId) throws SecurityException { + if (hasAdminPolicy(adminPolicy, callerPackageName)) { + return; } + enforcePermission(permission, callerPackageName, targetUserId); } /** @@ -22909,6 +23008,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { enforcePermission(permission, callerPackageName, targetUserId); } + private boolean hasAdminPolicy(int adminPolicy, String callerPackageName) { + CallerIdentity caller = getCallerIdentity(callerPackageName); + ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller); + return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy); + } + /** * Return whether the calling process has been granted permission to apply a device policy on * a specific user. @@ -22921,24 +23026,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { CallerIdentity caller = getCallerIdentity(callerPackageName); boolean hasPermissionOnOwnUser = hasPermission(permission, caller.getPackageName()); boolean hasPermissionOnTargetUser = true; - if (hasPermissionOnOwnUser & caller.getUserId() != targetUserId) { - hasPermissionOnTargetUser = hasPermission(CROSS_USER_PERMISSIONS.get(permission), - caller.getPackageName()); + if (hasPermissionOnOwnUser && caller.getUserId() != targetUserId) { + hasPermissionOnTargetUser = hasPermissionOnTargetUser + && hasPermission(CROSS_USER_PERMISSIONS.get(permission), + caller.getPackageName()); } return hasPermissionOnOwnUser && hasPermissionOnTargetUser; } - private boolean hasPermissionOrAdminPolicy(String permission, String callerPackageName, - int adminPolicy, int targetUserId) { - CallerIdentity caller = getCallerIdentity(callerPackageName); - if (hasPermission(permission, caller.getPackageName(), targetUserId)) { - return true; - } - ActiveAdmin deviceAdmin = getActiveAdminForCaller(null, caller); - return deviceAdmin != null && deviceAdmin.info.usesPolicy(adminPolicy); - } - /** * Return whether the calling process has been granted the given permission. * diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java index 509a66b4f8f7..8c2468af1146 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyDefinition.java @@ -284,6 +284,7 @@ final class PolicyDefinition<V> { private static final Map<String, PolicyDefinition<?>> POLICY_DEFINITIONS = new HashMap<>(); private static Map<String, Integer> USER_RESTRICTION_FLAGS = new HashMap<>(); + // TODO(b/277218360): Revisit policies that should be marked as global-only. static { POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.AUTO_TIMEZONE_POLICY, AUTO_TIMEZONE); POLICY_DEFINITIONS.put(DevicePolicyIdentifiers.PERMISSION_GRANT_POLICY, @@ -312,8 +313,9 @@ final class PolicyDefinition<V> { USER_RESTRICTION_FLAGS.put( UserManager.DISALLOW_WIFI_TETHERING, POLICY_FLAG_GLOBAL_ONLY_POLICY); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_GRANT_ADMIN, /* flags= */ 0); + // TODO: set as global only once we get rid of the mapping USER_RESTRICTION_FLAGS.put( - UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, POLICY_FLAG_GLOBAL_ONLY_POLICY); + UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI, /* flags= */ 0); USER_RESTRICTION_FLAGS.put( UserManager.DISALLOW_WIFI_DIRECT, POLICY_FLAG_GLOBAL_ONLY_POLICY); USER_RESTRICTION_FLAGS.put( @@ -333,8 +335,10 @@ final class PolicyDefinition<V> { USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_BLUETOOTH, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BLUETOOTH_SHARING, /* flags= */ 0); + // This effectively always applies globally, but it can be set on the profile + // parent, check the javadocs on the restriction for more info. USER_RESTRICTION_FLAGS.put( - UserManager.DISALLOW_USB_FILE_TRANSFER, POLICY_FLAG_GLOBAL_ONLY_POLICY); + UserManager.DISALLOW_USB_FILE_TRANSFER, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_CREDENTIALS, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_USER, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, /* flags= */ 0); @@ -344,8 +348,10 @@ final class PolicyDefinition<V> { USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CONFIG_DATE_TIME, /* flags= */ 0); USER_RESTRICTION_FLAGS.put( UserManager.DISALLOW_CONFIG_TETHERING, /* flags= */ 0); + // This effectively always applies globally, but it can be set on the profile + // parent, check the javadocs on the restriction for more info. USER_RESTRICTION_FLAGS.put( - UserManager.DISALLOW_NETWORK_RESET, POLICY_FLAG_GLOBAL_ONLY_POLICY); + UserManager.DISALLOW_NETWORK_RESET, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_FACTORY_RESET, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_USER, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_ADD_MANAGED_PROFILE, /* flags= */ 0); @@ -376,8 +382,7 @@ final class PolicyDefinition<V> { USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNMUTE_DEVICE, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_DATA_ROAMING, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_SET_USER_ICON, /* flags= */ 0); - // TODO: double check flags - USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, POLICY_FLAG_GLOBAL_ONLY_POLICY); + USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_OEM_UNLOCK, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_UNIFIED_PASSWORD, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.ALLOW_PARENT_PROFILE_APP_LINKING, /* flags= */ 0); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_AUTOFILL, /* flags= */ 0); @@ -390,6 +395,7 @@ final class PolicyDefinition<V> { USER_RESTRICTION_FLAGS.put( UserManager.DISALLOW_CONFIG_PRIVATE_DNS, POLICY_FLAG_GLOBAL_ONLY_POLICY); USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_MICROPHONE_TOGGLE, /* flags= */ 0); + // TODO: According the UserRestrictionsUtils, this is global only, need to confirm. USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_CAMERA_TOGGLE, /* flags= */ 0); // TODO: check if its global only USER_RESTRICTION_FLAGS.put(UserManager.DISALLOW_BIOMETRIC, /* flags= */ 0); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 492d477fe23a..b1d613109e09 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -125,6 +125,7 @@ import com.android.server.compat.PlatformCompatNative; import com.android.server.connectivity.PacProxyService; import com.android.server.contentcapture.ContentCaptureManagerInternal; import com.android.server.coverage.CoverageService; +import com.android.server.cpu.CpuMonitorService; import com.android.server.devicepolicy.DevicePolicyManagerService; import com.android.server.devicestate.DeviceStateManagerService; import com.android.server.display.DisplayManagerService; @@ -1405,6 +1406,15 @@ public final class SystemServer implements Dumpable { mSystemServiceManager.startService(RemoteProvisioningService.class); t.traceEnd(); + // TODO(b/277600174): Start CpuMonitorService on all builds and not just on debuggable + // builds once the Android JobScheduler starts using this service. + if (Build.IS_DEBUGGABLE || Build.IS_ENG) { + // Service for CPU monitor. + t.traceBegin("CpuMonitorService"); + mSystemServiceManager.startService(CpuMonitorService.class); + t.traceEnd(); + } + t.traceEnd(); // startCoreServices } diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java index 1a7517098d18..7b771aff0055 100644 --- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java @@ -567,36 +567,36 @@ public class RescuePartyTest { // Ensure that no action is taken for cases where the failure reason is unknown assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1), - PackageHealthObserverImpact.USER_IMPACT_NONE); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); // Ensure the correct user impact is returned for each mitigation count. assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), - PackageHealthObserverImpact.USER_IMPACT_LOW); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2), - PackageHealthObserverImpact.USER_IMPACT_LOW); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3), - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4), - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); } @Test public void testBootLoopLevels() { RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); - assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_NONE); - assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LOW); - assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LOW); - assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_HIGH); - assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_HIGH); - assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_HIGH); + assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); + assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); + assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); + assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); + assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50); + assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); } @Test 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 8211d6fc03a2..bc3f5ab5c42b 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java @@ -237,9 +237,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 +384,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 +411,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 +435,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,13 +471,14 @@ 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()); // Bumping past barrier makes us now runnable - airplaneRecord.terminalCount++; + airplaneRecord.setDeliveryState(0, BroadcastRecord.DELIVERY_DELIVERED, + "testRunnableAt_Ordered"); queue.invalidateRunnableAt(); assertTrue(queue.isRunnable()); assertNotEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason()); @@ -480,7 +495,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(); @@ -506,11 +521,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); } @@ -572,22 +587,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()); @@ -1163,8 +1178,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. @@ -1175,15 +1190,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 b6bc02a41c21..90e6a2d0f34a 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java @@ -16,6 +16,8 @@ package com.android.server.am; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_FINISH_RECEIVER; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_START_RECEIVER; import static android.os.UserHandle.USER_SYSTEM; import static com.android.server.am.ActivityManagerDebugConfig.LOG_WRITER_INFO; @@ -39,7 +41,6 @@ import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -463,7 +464,7 @@ public class BroadcastQueueTest { doAnswer((invocation) -> { Log.v(TAG, "Intercepting scheduleReceiver() for " - + Arrays.toString(invocation.getArguments())); + + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName); assertHealth(); final Intent intent = invocation.getArgument(0); final Bundle extras = invocation.getArgument(5); @@ -485,7 +486,7 @@ public class BroadcastQueueTest { doAnswer((invocation) -> { Log.v(TAG, "Intercepting scheduleRegisteredReceiver() for " - + Arrays.toString(invocation.getArguments())); + + Arrays.toString(invocation.getArguments()) + " package " + ai.packageName); assertHealth(); final Intent intent = invocation.getArgument(1); final Bundle extras = invocation.getArgument(4); @@ -961,7 +962,7 @@ public class BroadcastQueueTest { } else { // Confirm that app was thawed verify(mAms.mOomAdjuster, atLeastOnce()).unfreezeTemporarily( - eq(receiverApp), eq(OomAdjuster.OOM_ADJ_REASON_START_RECEIVER)); + eq(receiverApp), eq(OOM_ADJ_REASON_START_RECEIVER)); // Confirm that we added package to process verify(receiverApp, atLeastOnce()).addPackage(eq(receiverApp.info.packageName), @@ -1404,7 +1405,7 @@ public class BroadcastQueueTest { // Finally, verify that we thawed the final receiver verify(mAms.mOomAdjuster).unfreezeTemporarily(eq(callerApp), - eq(OomAdjuster.OOM_ADJ_REASON_FINISH_RECEIVER)); + eq(OOM_ADJ_REASON_FINISH_RECEIVER)); } /** @@ -1980,6 +1981,46 @@ public class BroadcastQueueTest { } /** + * Confirm how many times a pathological broadcast pattern results in OOM + * adjusts; watches for performance regressions. + */ + @Test + public void testOomAdjust_TriggerCount() throws Exception { + final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED); + + // Send 8 broadcasts, 4 receivers in the first process, + // and 2 alternating in each of the remaining processes + synchronized (mAms) { + for (int i = 0; i < 8; i++) { + final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED); + mQueue.enqueueBroadcastLocked(makeBroadcastRecord(intent, callerApp, + List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), + makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), + makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW), + makeManifestReceiver(PACKAGE_BLUE, CLASS_BLUE), + makeManifestReceiver(PACKAGE_YELLOW, CLASS_YELLOW)))); + } + } + waitForIdle(); + + final int expectedTimes; + switch (mImpl) { + // Original stack requested for every single receiver; yikes + case DEFAULT: expectedTimes = 64; break; + // Modern stack requests once each time we promote a process to + // running; we promote "green" twice, and "blue" and "yellow" once + case MODERN: expectedTimes = 4; break; + default: throw new UnsupportedOperationException(); + } + + verify(mAms, times(expectedTimes)) + .updateOomAdjPendingTargetsLocked(eq(OOM_ADJ_REASON_START_RECEIVER)); + } + + /** * Verify that expected events are triggered when a broadcast is finished. */ @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java index 2b6f2174d49b..08952eab071f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java @@ -24,7 +24,12 @@ import static android.content.Intent.ACTION_TIME_CHANGED; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY; import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE; -import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount; +import static com.android.server.am.BroadcastRecord.DELIVERY_DEFERRED; +import static com.android.server.am.BroadcastRecord.DELIVERY_DELIVERED; +import static com.android.server.am.BroadcastRecord.DELIVERY_PENDING; +import static com.android.server.am.BroadcastRecord.DELIVERY_SKIPPED; +import static com.android.server.am.BroadcastRecord.DELIVERY_TIMEOUT; +import static com.android.server.am.BroadcastRecord.calculateBlockedUntilBeyondCount; import static com.android.server.am.BroadcastRecord.calculateDeferUntilActive; import static com.android.server.am.BroadcastRecord.calculateUrgent; import static com.android.server.am.BroadcastRecord.isReceiverEquals; @@ -58,7 +63,6 @@ import androidx.test.filters.SmallTest; import com.android.server.am.BroadcastDispatcher.DeferredBootCompletedBroadcastPerUser; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -79,6 +83,7 @@ import java.util.function.BiFunction; @SmallTest @RunWith(MockitoJUnitRunner.class) public class BroadcastRecordTest { + private static final String TAG = "BroadcastRecordTest"; private static final int USER0 = UserHandle.USER_SYSTEM; private static final int USER1 = USER0 + 1; @@ -120,13 +125,13 @@ public class BroadcastRecordTest { assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10)))); assertArrayEquals(new int[] {-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), 0)), false)); assertArrayEquals(new int[] {-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), -10)), false)); assertArrayEquals(new int[] {-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), 10)), false)); } @@ -142,12 +147,12 @@ public class BroadcastRecordTest { createResolveInfo(PACKAGE3, getAppId(3), 10)))); assertArrayEquals(new int[] {-1,-1,-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), 0), createResolveInfo(PACKAGE2, getAppId(2), 0), createResolveInfo(PACKAGE3, getAppId(3), 0)), false)); assertArrayEquals(new int[] {-1,-1,-1}, - calculateBlockedUntilTerminalCount(List.of( + calculateBlockedUntilBeyondCount(List.of( createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 10), createResolveInfo(PACKAGE3, getAppId(3), 10)), false)); @@ -156,26 +161,176 @@ public class BroadcastRecordTest { @Test public void testIsPrioritized_Yes() { assertTrue(isPrioritized(List.of( - createResolveInfo(PACKAGE1, getAppId(1), -10), + createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 0), - createResolveInfo(PACKAGE3, getAppId(3), 10)))); + createResolveInfo(PACKAGE3, getAppId(3), -10)))); assertTrue(isPrioritized(List.of( - createResolveInfo(PACKAGE1, getAppId(1), 0), + createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 0), - createResolveInfo(PACKAGE3, getAppId(3), 10)))); + createResolveInfo(PACKAGE3, getAppId(3), 0)))); assertArrayEquals(new int[] {0,1,2}, - calculateBlockedUntilTerminalCount(List.of( - createResolveInfo(PACKAGE1, getAppId(1), -10), + calculateBlockedUntilBeyondCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), 10), createResolveInfo(PACKAGE2, getAppId(2), 0), - createResolveInfo(PACKAGE3, getAppId(3), 10)), false)); + createResolveInfo(PACKAGE3, getAppId(3), -10)), false)); assertArrayEquals(new int[] {0,0,2,3,3}, - calculateBlockedUntilTerminalCount(List.of( - createResolveInfo(PACKAGE1, getAppId(1), 0), - createResolveInfo(PACKAGE2, getAppId(2), 0), + calculateBlockedUntilBeyondCount(List.of( + createResolveInfo(PACKAGE1, getAppId(1), 20), + createResolveInfo(PACKAGE2, getAppId(2), 20), createResolveInfo(PACKAGE3, getAppId(3), 10), - createResolveInfo(PACKAGE3, getAppId(3), 20), - createResolveInfo(PACKAGE3, getAppId(3), 20)), false)); + createResolveInfo(PACKAGE3, getAppId(3), 0), + createResolveInfo(PACKAGE3, getAppId(3), 0)), false)); + } + + @Test + public void testSetDeliveryState_Single() { + final BroadcastRecord r = createBroadcastRecord( + new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of( + createResolveInfoWithPriority(0))); + assertEquals(DELIVERY_PENDING, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 0, 0, 0); + + r.setDeliveryState(0, DELIVERY_DEFERRED, TAG); + assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 0, 1, 1); + + // Identical state change has no effect + r.setDeliveryState(0, DELIVERY_DEFERRED, TAG); + assertEquals(DELIVERY_DEFERRED, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 0, 1, 1); + + // Moving to terminal state updates counters + r.setDeliveryState(0, DELIVERY_DELIVERED, TAG); + assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 1, 0, 1); + + // Trying to change terminal state has no effect + r.setDeliveryState(0, DELIVERY_TIMEOUT, TAG); + assertEquals(DELIVERY_DELIVERED, r.getDeliveryState(0)); + assertBlocked(r, false); + assertTerminalDeferredBeyond(r, 1, 0, 1); + } + + @Test + public void testSetDeliveryState_Unordered() { + final BroadcastRecord r = createBroadcastRecord( + new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of( + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0))); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 0, 0, 0); + + // Even though we finish a middle item in the tranche, we're not + // "beyond" it because there is still unfinished work before it + r.setDeliveryState(1, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 1, 0, 0); + + r.setDeliveryState(0, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 2, 0, 2); + + r.setDeliveryState(2, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 3, 0, 3); + } + + @Test + public void testSetDeliveryState_Ordered() { + final BroadcastRecord r = createOrderedBroadcastRecord( + new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of( + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0))); + assertBlocked(r, false, true, true); + assertTerminalDeferredBeyond(r, 0, 0, 0); + + r.setDeliveryState(0, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, true); + assertTerminalDeferredBeyond(r, 1, 0, 1); + + r.setDeliveryState(1, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 2, 0, 2); + + r.setDeliveryState(2, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false); + assertTerminalDeferredBeyond(r, 3, 0, 3); + } + + @Test + public void testSetDeliveryState_DeferUntilActive() { + final BroadcastRecord r = createBroadcastRecord( + new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED), List.of( + createResolveInfoWithPriority(10), + createResolveInfoWithPriority(10), + createResolveInfoWithPriority(10), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(0), + createResolveInfoWithPriority(-10), + createResolveInfoWithPriority(-10), + createResolveInfoWithPriority(-10))); + assertBlocked(r, false, false, false, true, true, true, true, true, true); + assertTerminalDeferredBeyond(r, 0, 0, 0); + + r.setDeliveryState(0, DELIVERY_PENDING, TAG); + r.setDeliveryState(1, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(2, DELIVERY_PENDING, TAG); + r.setDeliveryState(3, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(4, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(5, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(6, DELIVERY_DEFERRED, TAG); + r.setDeliveryState(7, DELIVERY_PENDING, TAG); + r.setDeliveryState(8, DELIVERY_DEFERRED, TAG); + + // Verify deferred counts ratchet up, but we're not "beyond" the first + // still-pending receiver + assertBlocked(r, false, false, false, true, true, true, true, true, true); + assertTerminalDeferredBeyond(r, 0, 6, 0); + + // We're still not "beyond" the first still-pending receiver, even when + // we finish a receiver later in the first tranche + r.setDeliveryState(2, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false, true, true, true, true, true, true); + assertTerminalDeferredBeyond(r, 1, 6, 0); + + // Completing that last item in first tranche means we now unblock the + // second tranche, and since it's entirely deferred, the third traunche + // is unblocked too + r.setDeliveryState(0, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 2, 6, 7); + + // Moving a deferred item in an earlier tranche back to being pending + // doesn't change the fact that we've already moved beyond it + r.setDeliveryState(1, DELIVERY_PENDING, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 2, 5, 7); + r.setDeliveryState(1, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 3, 5, 7); + + // Completing middle pending item is enough to fast-forward to end + r.setDeliveryState(7, DELIVERY_DELIVERED, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 4, 5, 9); + + // Moving everyone else directly into a finished state updates all the + // terminal counters + r.setDeliveryState(3, DELIVERY_SKIPPED, TAG); + r.setDeliveryState(4, DELIVERY_SKIPPED, TAG); + r.setDeliveryState(5, DELIVERY_SKIPPED, TAG); + r.setDeliveryState(6, DELIVERY_SKIPPED, TAG); + r.setDeliveryState(8, DELIVERY_SKIPPED, TAG); + assertBlocked(r, false, false, false, false, false, false, false, false, false); + assertTerminalDeferredBeyond(r, 9, 0, 9); } @Test @@ -688,6 +843,10 @@ public class BroadcastRecordTest { : errorMsg.insert(0, "Contains unexpected receiver: ").toString(); } + private static ResolveInfo createResolveInfoWithPriority(int priority) { + return createResolveInfo(PACKAGE1, getAppId(1), priority); + } + private static ResolveInfo createResolveInfo(String packageName, int uid) { return createResolveInfo(packageName, uid, 0); } @@ -738,21 +897,40 @@ public class BroadcastRecordTest { return excludedList; } + private BroadcastRecord createBroadcastRecord(Intent intent, + List<ResolveInfo> receivers) { + return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */, + null /* options */, false); + } + + private BroadcastRecord createOrderedBroadcastRecord(Intent intent, + List<ResolveInfo> receivers) { + return createBroadcastRecord(receivers, USER0, intent, null /* filterExtrasForReceiver */, + null /* options */, true); + } + private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, Intent intent) { return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */, - null /* options */); + null /* options */, false); } private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, Intent intent, BroadcastOptions options) { return createBroadcastRecord(receivers, userId, intent, null /* filterExtrasForReceiver */, - options); + options, false); } private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver, BroadcastOptions options) { + return createBroadcastRecord(receivers, userId, intent, filterExtrasForReceiver, + options, false); + } + + private BroadcastRecord createBroadcastRecord(List<ResolveInfo> receivers, int userId, + Intent intent, BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver, + BroadcastOptions options, boolean ordered) { return new BroadcastRecord( mQueue /* queue */, intent, @@ -774,7 +952,7 @@ public class BroadcastRecordTest { 0 /* resultCode */, null /* resultData */, null /* resultExtras */, - false /* serialized */, + ordered /* serialized */, false /* sticky */, false /* initialSticky */, userId, @@ -789,6 +967,20 @@ public class BroadcastRecordTest { private static boolean isPrioritized(List<Object> receivers) { return BroadcastRecord.isPrioritized( - calculateBlockedUntilTerminalCount(receivers, false), false); + calculateBlockedUntilBeyondCount(receivers, false), false); + } + + private static void assertBlocked(BroadcastRecord r, boolean... blocked) { + assertEquals(r.receivers.size(), blocked.length); + for (int i = 0; i < blocked.length; i++) { + assertEquals("blocked " + i, blocked[i], r.isBlocked(i)); + } + } + + private static void assertTerminalDeferredBeyond(BroadcastRecord r, + int expectedTerminalCount, int expectedDeferredCount, int expectedBeyondCount) { + assertEquals("terminal", expectedTerminalCount, r.terminalCount); + assertEquals("deferred", expectedDeferredCount, r.deferredCount); + assertEquals("beyond", expectedBeyondCount, r.beyondCount); } } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 485ce33dfb7d..cda5456723fb 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -38,11 +38,12 @@ import static android.app.ActivityManager.PROCESS_STATE_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.PROCESS_STATE_TOP_SLEEPING; import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_ACTIVITY; +import static android.app.ActivityManagerInternal.OOM_ADJ_REASON_NONE; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SHORT_SERVICE; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; -import static com.android.server.am.OomAdjuster.OOM_ADJ_REASON_ACTIVITY; import static com.android.server.am.ProcessList.BACKUP_APP_ADJ; import static com.android.server.am.ProcessList.CACHED_APP_MAX_ADJ; import static com.android.server.am.ProcessList.CACHED_APP_MIN_ADJ; @@ -254,12 +255,13 @@ public class MockingOomAdjusterTests { * - If there's only one process, then it calls updateOomAdjLocked(ProcessRecord, int). * - Otherwise, sets the processes to the LRU and run updateOomAdjLocked(int). */ + @SuppressWarnings("GuardedBy") private void updateOomAdj(ProcessRecord... apps) { if (apps.length == 1) { - sService.mOomAdjuster.updateOomAdjLocked(apps[0], OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(apps[0], OOM_ADJ_REASON_NONE); } else { setProcessesToLru(apps); - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); sService.mProcessList.getLruProcessesLOSP().clear(); } } @@ -658,7 +660,7 @@ public class MockingOomAdjusterTests { ServiceRecord s = bindService(app, system, null, Context.BIND_ALMOST_PERCEPTIBLE, mock(IBinder.class)); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); assertProcStates(app, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ + 1, SCHED_GROUP_DEFAULT); @@ -1226,7 +1228,7 @@ public class MockingOomAdjusterTests { mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj()); } @@ -1243,7 +1245,7 @@ public class MockingOomAdjusterTests { mock(IBinder.class)); client.mState.setMaxAdj(PERSISTENT_PROC_ADJ); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(app, OOM_ADJ_REASON_NONE); doReturn(false).when(wpc).isHeavyWeightProcess(); assertEquals(PERCEPTIBLE_APP_ADJ + 1, app.mState.getSetAdj()); @@ -1497,7 +1499,7 @@ public class MockingOomAdjusterTests { client2.mServices.setHasForegroundServices(false, 0, /* hasNoneType=*/false); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(client2, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(client2, OOM_ADJ_REASON_NONE); assertEquals(PROCESS_STATE_CACHED_EMPTY, client2.mState.getSetProcState()); assertEquals(PROCESS_STATE_CACHED_EMPTY, client.mState.getSetProcState()); @@ -1919,7 +1921,7 @@ public class MockingOomAdjusterTests { doReturn(client2).when(sService).getTopApp(); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(app2, OOM_ADJ_REASON_NONE); assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ, SCHED_GROUP_DEFAULT); } @@ -2029,7 +2031,7 @@ public class MockingOomAdjusterTests { setServiceMap(s3, MOCKAPP5_UID, cn3); setServiceMap(c2s, MOCKAPP3_UID, cn4); app2UidRecord.setIdle(false); - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ, SCHED_GROUP_DEFAULT); @@ -2055,7 +2057,7 @@ public class MockingOomAdjusterTests { anyInt(), anyBoolean(), anyBoolean(), anyBoolean()); doNothing().when(sService.mServices) .scheduleServiceTimeoutLocked(any(ProcessRecord.class)); - sService.mOomAdjuster.updateOomAdjLocked(client1, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(client1, OOM_ADJ_REASON_NONE); assertEquals(PROCESS_STATE_CACHED_EMPTY, client1.mState.getSetProcState()); assertEquals(PROCESS_STATE_SERVICE, app1.mState.getSetProcState()); @@ -2427,7 +2429,7 @@ public class MockingOomAdjusterTests { app2.mState.setHasShownUi(false); sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-ui-services"); assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj2, "cch-started-services"); @@ -2436,7 +2438,7 @@ public class MockingOomAdjusterTests { app.mState.setAdjType(null); app.mState.setSetAdj(UNKNOWN_ADJ); app.mState.setHasShownUi(false); - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); @@ -2445,7 +2447,7 @@ public class MockingOomAdjusterTests { app.mState.setAdjType(null); app.mState.setSetAdj(UNKNOWN_ADJ); s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1; - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services"); @@ -2463,7 +2465,7 @@ public class MockingOomAdjusterTests { s.lastActivity = now; app.mServices.startService(s); - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services"); @@ -2474,7 +2476,7 @@ public class MockingOomAdjusterTests { app.mState.setSetAdj(UNKNOWN_ADJ); app.mState.setHasShownUi(false); s.lastActivity = now - sService.mConstants.MAX_SERVICE_INACTIVITY - 1; - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); assertProcStates(app2, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services"); @@ -2482,7 +2484,7 @@ public class MockingOomAdjusterTests { doReturn(userOther).when(sService.mUserController).getCurrentUserId(); sService.mOomAdjuster.handleUserSwitchedLocked(); - sService.mOomAdjuster.updateOomAdjLocked(OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(OOM_ADJ_REASON_NONE); assertProcStates(app, true, PROCESS_STATE_SERVICE, cachedAdj1, "cch-started-services"); assertProcStates(app2, false, PROCESS_STATE_SERVICE, SERVICE_ADJ, "started-services"); } 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/location/gnss/GnssAntennaInfoProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java new file mode 100644 index 000000000000..e1fa8f527261 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssAntennaInfoProviderTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.gnss; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.location.LocationManager; +import android.location.LocationManagerInternal; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.location.gnss.hal.FakeGnssHal; +import com.android.server.location.gnss.hal.GnssNative; +import com.android.server.location.injector.Injector; +import com.android.server.location.injector.TestInjector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Objects; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class GnssAntennaInfoProviderTest { + private @Mock Context mContext; + private @Mock LocationManagerInternal mInternal; + private @Mock GnssConfiguration mMockConfiguration; + private @Mock IBinder mBinder; + private GnssNative mGnssNative; + + private GnssAntennaInfoProvider mTestProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER), + anyInt()); + LocalServices.addService(LocationManagerInternal.class, mInternal); + FakeGnssHal fakeGnssHal = new FakeGnssHal(); + GnssNative.setGnssHalForTest(fakeGnssHal); + Injector injector = new TestInjector(mContext); + mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration))); + mTestProvider = new GnssAntennaInfoProvider(mGnssNative); + mGnssNative.register(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(LocationManagerInternal.class); + } + + @Test + public void testOnHalStarted() { + verify(mGnssNative, times(1)).startAntennaInfoListening(); + } + + @Test + public void testOnHalRestarted() { + mTestProvider.onHalRestarted(); + verify(mGnssNative, times(2)).startAntennaInfoListening(); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java index fd9dfe869d52..bf96b1dec4ac 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java @@ -74,7 +74,6 @@ public class GnssMeasurementsProviderTest { private @Mock Context mContext; private @Mock LocationManagerInternal mInternal; private @Mock GnssConfiguration mMockConfiguration; - private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks; private @Mock IGnssMeasurementsListener mListener1; private @Mock IGnssMeasurementsListener mListener2; private @Mock IBinder mBinder1; @@ -98,7 +97,6 @@ public class GnssMeasurementsProviderTest { Injector injector = new TestInjector(mContext); mGnssNative = spy(Objects.requireNonNull( GnssNative.create(injector, mMockConfiguration))); - mGnssNative.setGeofenceCallbacks(mGeofenceCallbacks); mTestProvider = new GnssMeasurementsProvider(injector, mGnssNative); mGnssNative.register(); } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java new file mode 100644 index 000000000000..64aa4b3fa2ff --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNavigationMessageProviderTest.java @@ -0,0 +1,93 @@ +/* + * 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.location.gnss; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.location.IGnssNavigationMessageListener; +import android.location.LocationManager; +import android.location.LocationManagerInternal; +import android.location.util.identity.CallerIdentity; +import android.os.IBinder; + +import com.android.server.LocalServices; +import com.android.server.location.gnss.hal.FakeGnssHal; +import com.android.server.location.gnss.hal.GnssNative; +import com.android.server.location.injector.FakeUserInfoHelper; +import com.android.server.location.injector.Injector; +import com.android.server.location.injector.TestInjector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Objects; + +public class GnssNavigationMessageProviderTest { + private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID; + private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000, + "mypackage", "attribution", "listener"); + private @Mock Context mContext; + private @Mock LocationManagerInternal mInternal; + private @Mock GnssConfiguration mMockConfiguration; + private @Mock IGnssNavigationMessageListener mListener; + private @Mock IBinder mBinder; + + private GnssNative mGnssNative; + + private GnssNavigationMessageProvider mTestProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doReturn(mBinder).when(mListener).asBinder(); + doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER), + anyInt()); + LocalServices.addService(LocationManagerInternal.class, mInternal); + FakeGnssHal fakeGnssHal = new FakeGnssHal(); + GnssNative.setGnssHalForTest(fakeGnssHal); + Injector injector = new TestInjector(mContext); + mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration))); + mTestProvider = new GnssNavigationMessageProvider(injector, mGnssNative); + mGnssNative.register(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(LocationManagerInternal.class); + } + + @Test + public void testAddListener() { + // add a request + mTestProvider.addListener(IDENTITY, mListener); + verify(mGnssNative, times(1)).startNavigationMessageCollection(); + + // remove a request + mTestProvider.removeListener(mListener); + verify(mGnssNative, times(1)).stopNavigationMessageCollection(); + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java new file mode 100644 index 000000000000..49e5e69933f9 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssNmeaProviderTest.java @@ -0,0 +1,104 @@ +/* + * 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.location.gnss; + + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.location.IGnssNmeaListener; +import android.location.LocationManager; +import android.location.LocationManagerInternal; +import android.location.util.identity.CallerIdentity; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.server.LocalServices; +import com.android.server.location.gnss.hal.FakeGnssHal; +import com.android.server.location.gnss.hal.GnssNative; +import com.android.server.location.injector.FakeUserInfoHelper; +import com.android.server.location.injector.Injector; +import com.android.server.location.injector.TestInjector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Objects; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class GnssNmeaProviderTest { + + private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID; + private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000, + "mypackage", "attribution", "listener"); + private @Mock Context mContext; + private @Mock LocationManagerInternal mInternal; + private @Mock GnssConfiguration mMockConfiguration; + private @Mock IGnssNmeaListener mListener; + private @Mock IBinder mBinder; + + private GnssNative mGnssNative; + + private GnssNmeaProvider mTestProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doReturn(mBinder).when(mListener).asBinder(); + doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER), + anyInt()); + LocalServices.addService(LocationManagerInternal.class, mInternal); + FakeGnssHal fakeGnssHal = new FakeGnssHal(); + GnssNative.setGnssHalForTest(fakeGnssHal); + Injector injector = new TestInjector(mContext); + mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration))); + mTestProvider = new GnssNmeaProvider(injector, mGnssNative); + mGnssNative.register(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(LocationManagerInternal.class); + } + + @Test + public void testAddListener() { + // add a request + mTestProvider.addListener(IDENTITY, mListener); + verify(mGnssNative, times(1)).startNmeaMessageCollection(); + + // remove a request + mTestProvider.removeListener(mListener); + verify(mGnssNative, times(1)).stopNmeaMessageCollection(); + } + +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java new file mode 100644 index 000000000000..ce2aec7f8d5d --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssStatusProviderTest.java @@ -0,0 +1,103 @@ +/* + * 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.location.gnss; + + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.location.IGnssStatusListener; +import android.location.LocationManager; +import android.location.LocationManagerInternal; +import android.location.util.identity.CallerIdentity; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.server.LocalServices; +import com.android.server.location.gnss.hal.FakeGnssHal; +import com.android.server.location.gnss.hal.GnssNative; +import com.android.server.location.injector.FakeUserInfoHelper; +import com.android.server.location.injector.Injector; +import com.android.server.location.injector.TestInjector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Objects; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class GnssStatusProviderTest { + private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID; + private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000, + "mypackage", "attribution", "listener"); + private @Mock Context mContext; + private @Mock LocationManagerInternal mInternal; + private @Mock GnssConfiguration mMockConfiguration; + private @Mock IGnssStatusListener mListener; + private @Mock IBinder mBinder; + + private GnssNative mGnssNative; + + private GnssStatusProvider mTestProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doReturn(mBinder).when(mListener).asBinder(); + doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER), + anyInt()); + LocalServices.addService(LocationManagerInternal.class, mInternal); + FakeGnssHal fakeGnssHal = new FakeGnssHal(); + GnssNative.setGnssHalForTest(fakeGnssHal); + Injector injector = new TestInjector(mContext); + mGnssNative = spy(Objects.requireNonNull(GnssNative.create(injector, mMockConfiguration))); + mTestProvider = new GnssStatusProvider(injector, mGnssNative); + mGnssNative.register(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(LocationManagerInternal.class); + } + + @Test + public void testAddListener() { + // add a request + mTestProvider.addListener(IDENTITY, mListener); + verify(mGnssNative, times(1)).startSvStatusCollection(); + + // remove a request + mTestProvider.removeListener(mListener); + verify(mGnssNative, times(1)).stopSvStatusCollection(); + } + +} diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java index b7ab6f80167e..2d962acfe665 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/hal/FakeGnssHal.java @@ -562,6 +562,26 @@ public final class FakeGnssHal extends GnssNative.GnssHal { } @Override + protected boolean startSvStatusCollection() { + return true; + } + + @Override + protected boolean stopSvStatusCollection() { + return true; + } + + @Override + public boolean startNmeaMessageCollection() { + return true; + } + + @Override + public boolean stopNmeaMessageCollection() { + return true; + } + + @Override protected int getBatchSize() { return mBatchSize; } diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java index 0be678af12dc..541b07782b29 100644 --- a/services/tests/servicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/rollback/RollbackPackageHealthObserverTest.java @@ -16,31 +16,65 @@ package com.android.server.rollback; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; + import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; import android.content.pm.VersionedPackage; +import android.content.rollback.PackageRollbackInfo; +import android.content.rollback.RollbackInfo; +import android.content.rollback.RollbackManager; import android.util.Log; import android.util.Xml; import androidx.test.runner.AndroidJUnit4; +import com.android.dx.mockito.inline.extended.ExtendedMockito; +import com.android.server.PackageWatchdog; import com.android.server.SystemConfig; +import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.Mock; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; +import org.mockito.stubbing.Answer; import org.xmlpull.v1.XmlPullParser; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.util.List; import java.util.Scanner; + @RunWith(AndroidJUnit4.class) public class RollbackPackageHealthObserverTest { + @Mock + private Context mMockContext; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PackageWatchdog mMockPackageWatchdog; + @Mock + RollbackManager mRollbackManager; + @Mock + RollbackInfo mRollbackInfo; + @Mock + PackageRollbackInfo mPackageRollbackInfo; + + private MockitoSession mSession; + private static final String APP_A = "com.package.a"; + private static final long VERSION_CODE = 1L; private static final String LOG_TAG = "RollbackPackageHealthObserverTest"; private SystemConfig mSysConfig; @@ -50,17 +84,74 @@ public class RollbackPackageHealthObserverTest { @Before public void setup() { mSysConfig = new SystemConfigTestClass(); + + mSession = ExtendedMockito.mockitoSession() + .initMocks(this) + .strictness(Strictness.LENIENT) + .spyStatic(PackageWatchdog.class) + .startMocking(); + + // Mock PackageWatchdog + doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog) + .when(() -> PackageWatchdog.getInstance(mMockContext)); + + } + + @After + public void tearDown() throws Exception { + mSession.finishMocking(); } /** - * Subclass of SystemConfig without running the constructor. - */ + * Subclass of SystemConfig without running the constructor. + */ private class SystemConfigTestClass extends SystemConfig { SystemConfigTestClass() { - super(false); + super(false); } } + @Test + public void testHealthCheckLevels() { + RollbackPackageHealthObserver observer = + spy(new RollbackPackageHealthObserver(mMockContext)); + VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE); + + + when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager); + + // Crashes with no rollbacks available + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, + observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1)); + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, + observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_APP_CRASH, 1)); + + // Make the rollbacks available + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo)); + when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo)); + when(mPackageRollbackInfo.getVersionRolledBackFrom()).thenReturn(testFailedPackage); + + // native crash + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, + observer.onHealthCheckFailed(null, + PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1)); + // non-native crash + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30, + observer.onHealthCheckFailed(testFailedPackage, + PackageWatchdog.FAILURE_REASON_APP_CRASH, 1)); + // Second non-native crash again + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70, + observer.onHealthCheckFailed(testFailedPackage, + PackageWatchdog.FAILURE_REASON_APP_CRASH, 2)); + // Subsequent crashes when rollbacks have completed + when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of()); + assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0, + observer.onHealthCheckFailed(testFailedPackage, + PackageWatchdog.FAILURE_REASON_APP_CRASH, 3)); + } + /** * Test that isAutomaticRollbackDenied works correctly when packages that are not * denied are sent. @@ -77,7 +168,7 @@ public class RollbackPackageHealthObserverTest { readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, - new VersionedPackage("com.test.package", 1))).isEqualTo(false); + new VersionedPackage("com.test.package", 1))).isEqualTo(false); } /** @@ -96,7 +187,7 @@ public class RollbackPackageHealthObserverTest { readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, - new VersionedPackage("com.android.vending", 1))).isEqualTo(true); + new VersionedPackage("com.android.vending", 1))).isEqualTo(true); } /** @@ -109,7 +200,7 @@ public class RollbackPackageHealthObserverTest { readPermissions(folder, /* Grant all permission flags */ ~0); assertThat(RollbackPackageHealthObserver.isAutomaticRollbackDenied(mSysConfig, - new VersionedPackage("com.android.vending", 1))).isEqualTo(false); + new VersionedPackage("com.android.vending", 1))).isEqualTo(false); } /** diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index dbf5021d3c6b..26a3ae110525 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -43,6 +43,7 @@ import android.annotation.NonNull; import android.app.admin.DevicePolicyManager; import android.app.trust.ITrustManager; import android.content.Context; +import android.content.res.Resources; import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.IBiometricAuthenticator; import android.hardware.biometrics.IBiometricSensorReceiver; @@ -52,6 +53,7 @@ import android.hardware.biometrics.PromptInfo; import android.hardware.biometrics.SensorProperties; import android.hardware.face.FaceSensorProperties; import android.hardware.face.FaceSensorPropertiesInternal; +import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Binder; @@ -83,6 +85,7 @@ public class AuthSessionTest { private static final long TEST_REQUEST_ID = 22; @Mock private Context mContext; + @Mock private Resources mResources; @Mock private BiometricContext mBiometricContext; @Mock private ITrustManager mTrustManager; @Mock private DevicePolicyManager mDevicePolicyManager; @@ -104,6 +107,7 @@ public class AuthSessionTest { @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + when(mContext.getResources()).thenReturn(mResources); when(mClientReceiver.asBinder()).thenReturn(mock(Binder.class)); when(mBiometricContext.updateContext(any(), anyBoolean())) .thenAnswer(invocation -> invocation.getArgument(0)); @@ -342,6 +346,33 @@ public class AuthSessionTest { testInvokesCancel(session -> session.onDialogDismissed(DISMISSED_REASON_NEGATIVE, null)); } + @Test + public void testCallbackOnAcquired() throws RemoteException { + final String acquiredStr = "test_acquired_info_callback"; + final String acquiredStrVendor = "test_acquired_info_callback_vendor"; + setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_REAR); + + final AuthSession session = createAuthSession(mSensors, + false /* checkDevicePolicyManager */, + Authenticators.BIOMETRIC_STRONG, + TEST_REQUEST_ID, + 0 /* operationId */, + 0 /* userId */); + + when(mContext.getString(com.android.internal.R.string.fingerprint_acquired_partial)) + .thenReturn(acquiredStr); + session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_PARTIAL, 0); + verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStr)); + verify(mClientReceiver).onAcquired(eq(1), eq(acquiredStr)); + + when(mResources.getStringArray(com.android.internal.R.array.fingerprint_acquired_vendor)) + .thenReturn(new String[]{acquiredStrVendor}); + session.onAcquired(0, FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR, 0); + verify(mStatusBarService).onBiometricHelp(anyInt(), eq(acquiredStrVendor)); + verify(mClientReceiver).onAcquired( + eq(FingerprintManager.FINGERPRINT_ACQUIRED_VENDOR_BASE), eq(acquiredStrVendor)); + } + // TODO (b/208484275) : Enable these tests // @Test // public void testPreAuth_canAuthAndPrivacyDisabled() throws Exception { 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/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java index 550204b99323..4cfbb9520d5f 100644 --- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java @@ -105,6 +105,7 @@ public class LocaleManagerServiceTest { mMockPackageManager = mock(PackageManager.class); mMockPackageMonitor = mock(PackageMonitor.class); + doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt()); // For unit tests, set the default installer info doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager) .getInstallSourceInfo(anyString()); diff --git a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java index da9de2562930..e20f1e7065d4 100644 --- a/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java +++ b/services/tests/servicestests/src/com/android/server/locales/SystemAppUpdateTrackerTest.java @@ -131,6 +131,7 @@ public class SystemAppUpdateTrackerTest { doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); doReturn(InstrumentationRegistry.getContext().getContentResolver()) .when(mMockContext).getContentResolver(); + doReturn(mMockContext).when(mMockContext).createContextAsUser(any(), anyInt()); mStoragefile = new AtomicFile(new File( Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml")); 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/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java index ff6c9769b69f..516fb4aa40c6 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java @@ -15,23 +15,31 @@ */ package com.android.server.notification; -import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY; +import static android.app.Notification.FLAG_AUTO_CANCEL; +import static android.app.Notification.FLAG_BUBBLE; +import static android.app.Notification.FLAG_CAN_COLORIZE; +import static android.app.Notification.FLAG_FOREGROUND_SERVICE; +import static android.app.Notification.FLAG_NO_CLEAR; +import static android.app.Notification.FLAG_ONGOING_EVENT; + +import static com.android.server.notification.GroupHelper.BASE_FLAGS; import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertNotNull; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import android.annotation.SuppressLint; import android.app.Notification; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; +import android.util.ArrayMap; import androidx.test.runner.AndroidJUnit4; @@ -45,11 +53,10 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; @SmallTest +@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class. @RunWith(AndroidJUnit4.class) public class GroupHelperTest extends UiServiceTestCase { private @Mock GroupHelper.Callback mCallback; @@ -82,21 +89,104 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - public void testNoGroup_postingUnderLimit() throws Exception { + public void testGetAutogroupSummaryFlags_noChildren() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + + assertEquals(BASE_FLAGS, mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_oneOngoing() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", 0); + children.put("b", FLAG_ONGOING_EVENT); + children.put("c", FLAG_BUBBLE); + + assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_oneOngoingNoClear() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", 0); + children.put("b", FLAG_ONGOING_EVENT|FLAG_NO_CLEAR); + children.put("c", FLAG_BUBBLE); + + assertEquals(FLAG_NO_CLEAR | FLAG_ONGOING_EVENT | BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_oneOngoingBubble() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", 0); + children.put("b", FLAG_ONGOING_EVENT | FLAG_BUBBLE); + children.put("c", FLAG_BUBBLE); + + assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_multipleOngoing() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", 0); + children.put("b", FLAG_ONGOING_EVENT); + children.put("c", FLAG_BUBBLE); + children.put("d", FLAG_ONGOING_EVENT); + + assertEquals(FLAG_ONGOING_EVENT | BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_oneAutoCancel() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", 0); + children.put("b", FLAG_AUTO_CANCEL); + children.put("c", FLAG_BUBBLE); + + assertEquals(BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_allAutoCancel() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", FLAG_AUTO_CANCEL); + children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE); + children.put("c", FLAG_AUTO_CANCEL); + children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE); + + assertEquals(FLAG_AUTO_CANCEL | BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testGetAutogroupSummaryFlags_allAutoCancelOneOngoing() { + ArrayMap<String, Integer> children = new ArrayMap<>(); + children.put("a", FLAG_AUTO_CANCEL); + children.put("b", FLAG_AUTO_CANCEL | FLAG_CAN_COLORIZE); + children.put("c", FLAG_AUTO_CANCEL); + children.put("d", FLAG_AUTO_CANCEL | FLAG_FOREGROUND_SERVICE | FLAG_ONGOING_EVENT); + + assertEquals(FLAG_AUTO_CANCEL| FLAG_ONGOING_EVENT | BASE_FLAGS, + mGroupHelper.getAutogroupSummaryFlags(children)); + } + + @Test + public void testNoGroup_postingUnderLimit() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false); } - verify(mCallback, never()).addAutoGroupSummary( - eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean()); - verify(mCallback, never()).addAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verifyZeroInteractions(mCallback); } @Test - public void testNoGroup_multiPackage() throws Exception { + public void testNoGroup_multiPackage() { final String pkg = "package"; final String pkg2 = "package2"; for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { @@ -105,31 +195,23 @@ public class GroupHelperTest extends UiServiceTestCase { } mGroupHelper.onNotificationPosted( getSbn(pkg2, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM), false); - verify(mCallback, never()).addAutoGroupSummary( - eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean()); - verify(mCallback, never()).addAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verifyZeroInteractions(mCallback); } @Test - public void testNoGroup_multiUser() throws Exception { + public void testNoGroup_multiUser() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { mGroupHelper.onNotificationPosted(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false); } mGroupHelper.onNotificationPosted( - getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.ALL), false); - verify(mCallback, never()).addAutoGroupSummary( - anyInt(), eq(pkg), anyString(), anyBoolean()); - verify(mCallback, never()).addAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.of(7)), false); + verifyZeroInteractions(mCallback); } @Test - public void testNoGroup_someAreGrouped() throws Exception { + public void testNoGroup_someAreGrouped() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) { mGroupHelper.onNotificationPosted( @@ -137,233 +219,344 @@ public class GroupHelperTest extends UiServiceTestCase { } mGroupHelper.onNotificationPosted( getSbn(pkg, AUTOGROUP_AT_COUNT, "four", UserHandle.SYSTEM, "a"), false); - verify(mCallback, never()).addAutoGroupSummary( - eq(UserHandle.USER_SYSTEM), eq(pkg), anyString(), anyBoolean()); - verify(mCallback, never()).addAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroup(anyString()); - verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verifyZeroInteractions(mCallback); } @Test - public void testPostingOverLimit() throws Exception { + public void testAddSummary() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { mGroupHelper.onNotificationPosted( getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM), false); } - verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false)); + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS)); verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); } @Test - public void testPostingOverLimit_addsOngoingFlag() throws Exception { + public void testAddSummary_oneChildOngoing_summaryOngoing() { final String pkg = "package"; for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); if (i == 0) { - sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; } mGroupHelper.onNotificationPosted(sbn, false); } - verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(true)); + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(BASE_FLAGS | FLAG_ONGOING_EVENT)); verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); } @Test - public void testAutoGroupCount_addingNoGroupSBN() { + public void testAddSummary_oneChildAutoCancel_summaryNotAutoCancel() { final String pkg = "package"; - ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + if (i == 0) { + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + } + mGroupHelper.onNotificationPosted(sbn, false); } + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS)); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); + } - for (StatusBarNotification sbn: notifications) { - sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - sbn.setOverrideGroupKey(AUTOGROUP_KEY); + @Test + public void testAddSummary_allChildrenAutoCancel_summaryAutoCancel() { + final String pkg = "package"; + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + mGroupHelper.onNotificationPosted(sbn, false); } + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(BASE_FLAGS | FLAG_AUTO_CANCEL)); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); + } - for (StatusBarNotification sbn: notifications) { - mGroupHelper.onNotificationPosted(sbn, true); + @Test + public void testAddSummary_summaryAutoCancelNoClear() { + final String pkg = "package"; + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + if (i == 0) { + sbn.getNotification().flags |= FLAG_NO_CLEAR; + } + mGroupHelper.onNotificationPosted(sbn, false); } - - verify(mCallback, times(AUTOGROUP_AT_COUNT + 1)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); - - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount( - userId, pkg), AUTOGROUP_AT_COUNT + 1); + verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), + eq(BASE_FLAGS | FLAG_AUTO_CANCEL | FLAG_NO_CLEAR)); + verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroup(anyString()); + verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); } @Test - public void testAutoGroupCount_UpdateNotification() { + public void testAutoGrouped_allOngoing_updateChildNotOngoing() { final String pkg = "package"; - ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); - } - for (StatusBarNotification sbn: notifications) { - sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - sbn.setOverrideGroupKey(AUTOGROUP_KEY); + // Post AUTOGROUP_AT_COUNT ongoing notifications + ArrayList<StatusBarNotification> notifications = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; + notifications.add(sbn); } for (StatusBarNotification sbn: notifications) { - mGroupHelper.onNotificationPosted(sbn, true); + mGroupHelper.onNotificationPosted(sbn, false); } - notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT; - mGroupHelper.onNotificationUpdated(notifications.get(0)); + // One notification is no longer ongoing + notifications.get(0).getNotification().flags &= ~FLAG_ONGOING_EVENT; + mGroupHelper.onNotificationPosted(notifications.get(0), true); - verify(mCallback, times(AUTOGROUP_AT_COUNT + 2)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); - - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount( - userId, pkg), AUTOGROUP_AT_COUNT); + // Summary should keep FLAG_ONGOING_EVENT if any child has it + verify(mCallback).updateAutogroupSummary( + anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT)); } @Test - public void testAutoGroupCount_UpdateNotificationAfterChanges() { + public void testAutoGrouped_singleOngoing_removeOngoingChild() { final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + if (i == 0) { + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; + } + notifications.add(sbn); } for (StatusBarNotification sbn: notifications) { - sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - sbn.setOverrideGroupKey(AUTOGROUP_KEY); + mGroupHelper.onNotificationPosted(sbn, false); } - for (StatusBarNotification sbn: notifications) { - mGroupHelper.onNotificationPosted(sbn, true); - } + // remove ongoing + mGroupHelper.onNotificationRemoved(notifications.get(0)); - notifications.get(0).getNotification().flags &= ~Notification.FLAG_ONGOING_EVENT; + // Summary is no longer ongoing + verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS)); + } - mGroupHelper.onNotificationUpdated(notifications.get(0)); + @Test + public void testAutoGrouped_noOngoing_updateOngoingChild() { + final String pkg = "package"; - notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT; + // Post AUTOGROUP_AT_COUNT ongoing notifications + ArrayList<StatusBarNotification> notifications = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + notifications.add(sbn); + } - mGroupHelper.onNotificationUpdated(notifications.get(0)); + for (StatusBarNotification sbn: notifications) { + mGroupHelper.onNotificationPosted(sbn, false); + } - verify(mCallback, times(AUTOGROUP_AT_COUNT + 3)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); + // update to ongoing + notifications.get(0).getNotification().flags |= FLAG_ONGOING_EVENT; + mGroupHelper.onNotificationPosted(notifications.get(0), true); - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount( - userId, pkg), AUTOGROUP_AT_COUNT + 1); + // Summary is now ongoing + verify(mCallback).updateAutogroupSummary( + anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT)); } @Test - public void testAutoGroupCount_RemoveNotification() { + public void testAutoGrouped_noOngoing_addOngoingChild() { final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + notifications.add(sbn); } for (StatusBarNotification sbn: notifications) { - sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - sbn.setOverrideGroupKey(AUTOGROUP_KEY); + mGroupHelper.onNotificationPosted(sbn, false); } - for (StatusBarNotification sbn: notifications) { - mGroupHelper.onNotificationPosted(sbn, true); + // add ongoing + StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT + 1, null, UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; + mGroupHelper.onNotificationPosted(sbn, true); + + // Summary is now ongoing + verify(mCallback).updateAutogroupSummary( + anyInt(), anyString(), eq(BASE_FLAGS | FLAG_ONGOING_EVENT)); + } + + @Test + public void testAutoGrouped_singleOngoing_appGroupOngoingChild() { + final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications + ArrayList<StatusBarNotification> notifications = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + if (i == 0) { + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; + } + notifications.add(sbn); } - mGroupHelper.onNotificationRemoved(notifications.get(0)); + for (StatusBarNotification sbn: notifications) { + mGroupHelper.onNotificationPosted(sbn, false); + } - verify(mCallback, times(AUTOGROUP_AT_COUNT + 2)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); + // app group the ongoing child + StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now"); + mGroupHelper.onNotificationPosted(sbn, true); - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount( - userId, pkg), AUTOGROUP_AT_COUNT); + // Summary is no longer ongoing + verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS)); } - @Test - public void testAutoGroupCount_UpdateToNoneOngoingNotification() { + public void testAutoGrouped_singleOngoing_removeNonOngoingChild() { final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + if (i == 0) { + sbn.getNotification().flags |= FLAG_ONGOING_EVENT; + } + notifications.add(sbn); } for (StatusBarNotification sbn: notifications) { - sbn.setOverrideGroupKey(AUTOGROUP_KEY); + mGroupHelper.onNotificationPosted(sbn, false); } - for (StatusBarNotification sbn: notifications) { - mGroupHelper.onNotificationPosted(sbn, true); + // remove ongoing + mGroupHelper.onNotificationRemoved(notifications.get(1)); + + // Summary is still ongoing + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); + } + + @Test + public void testAutoGrouped_allAutoCancel_updateChildNotAutoCancel() { + final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications + ArrayList<StatusBarNotification> notifications = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + notifications.add(sbn); } - notifications.get(0).getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - mGroupHelper.onNotificationUpdated(notifications.get(0)); + for (StatusBarNotification sbn: notifications) { + mGroupHelper.onNotificationPosted(sbn, false); + } - verify(mCallback, times(1)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); + // One notification is no longer autocancelable + notifications.get(0).getNotification().flags &= ~FLAG_AUTO_CANCEL; + mGroupHelper.onNotificationPosted(notifications.get(0), true); - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount( - userId, pkg), 1); + // Summary should no longer be autocancelable + verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS)); } @Test - public void testAutoGroupCount_AddOneOngoingNotification() { + public void testAutoGrouped_almostAllAutoCancel_updateChildAutoCancel() { final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + if (i != 0) { + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + } + notifications.add(sbn); } - StatusBarNotification sbn = notifications.get(AUTOGROUP_AT_COUNT); - sbn.getNotification().flags |= Notification.FLAG_ONGOING_EVENT; - sbn.setOverrideGroupKey(AUTOGROUP_KEY); - - for (StatusBarNotification current: notifications) { - mGroupHelper.onNotificationPosted(current, true); + for (StatusBarNotification sbn: notifications) { + mGroupHelper.onNotificationPosted(sbn, false); } - verify(mCallback, times(1)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); + // Missing notification is now autocancelable + notifications.get(0).getNotification().flags |= FLAG_AUTO_CANCEL; + mGroupHelper.onNotificationPosted(notifications.get(0), true); - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount( - userId, pkg), 1); + // Summary should now autocancelable + verify(mCallback).updateAutogroupSummary( + anyInt(), anyString(), eq(BASE_FLAGS | FLAG_AUTO_CANCEL)); } @Test - public void testAutoGroupCount_UpdateNoneOngoing() { + public void testAutoGrouped_allAutoCancel_updateChildAppGrouped() { final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications ArrayList<StatusBarNotification> notifications = new ArrayList<>(); - for (int i = 0; i < AUTOGROUP_AT_COUNT + 1; i++) { - notifications.add(getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM)); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + notifications.add(sbn); } for (StatusBarNotification sbn: notifications) { - sbn.setOverrideGroupKey(AUTOGROUP_KEY); + mGroupHelper.onNotificationPosted(sbn, false); + } + + // One notification is now grouped by app + StatusBarNotification sbn = getSbn(pkg, 0, "0", UserHandle.SYSTEM, "app group now"); + mGroupHelper.onNotificationPosted(sbn, true); + + // Summary should be still be autocancelable + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); + } + + @Test + public void testAutoGrouped_allAutoCancel_removeChild() { + final String pkg = "package"; + + // Post AUTOGROUP_AT_COUNT ongoing notifications + ArrayList<StatusBarNotification> notifications = new ArrayList<>(); + for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { + StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM); + sbn.getNotification().flags |= FLAG_AUTO_CANCEL; + notifications.add(sbn); } for (StatusBarNotification sbn: notifications) { - mGroupHelper.onNotificationPosted(sbn, true); + mGroupHelper.onNotificationPosted(sbn, false); } - verify(mCallback, times(0)) - .updateAutogroupSummary(anyInt(), anyString(), eq(true)); + mGroupHelper.onNotificationRemoved(notifications.get(0)); - int userId = UserHandle.SYSTEM.getIdentifier(); - assertEquals(mGroupHelper.getOngoingGroupCount(userId, pkg), 0); + // Summary should still be autocancelable + verify(mCallback, never()).updateAutogroupSummary(anyInt(), anyString(), anyInt()); } - @Test - public void testDropToZeroRemoveGroup() throws Exception { + public void testDropToZeroRemoveGroup() { final String pkg = "package"; List<StatusBarNotification> posted = new ArrayList<>(); for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -371,7 +564,8 @@ public class GroupHelperTest extends UiServiceTestCase { posted.add(sbn); mGroupHelper.onNotificationPosted(sbn, false); } - verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false)); + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS)); verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); @@ -390,7 +584,7 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - public void testAppStartsGrouping() throws Exception { + public void testAppStartsGrouping() { final String pkg = "package"; List<StatusBarNotification> posted = new ArrayList<>(); for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -399,7 +593,7 @@ public class GroupHelperTest extends UiServiceTestCase { mGroupHelper.onNotificationPosted(sbn, false); } verify(mCallback, times(1)).addAutoGroupSummary( - anyInt(), eq(pkg), anyString(), anyBoolean()); + anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS)); verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); @@ -408,9 +602,10 @@ public class GroupHelperTest extends UiServiceTestCase { for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, "app group"); - mGroupHelper.onNotificationPosted(sbn, false); + sbn.setOverrideGroupKey("autogrouped"); + mGroupHelper.onNotificationPosted(sbn, true); verify(mCallback, times(1)).removeAutoGroup(sbn.getKey()); - if (i < AUTOGROUP_AT_COUNT -1) { + if (i < AUTOGROUP_AT_COUNT - 1) { verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); } } @@ -418,8 +613,7 @@ public class GroupHelperTest extends UiServiceTestCase { } @Test - public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() - throws Exception { + public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled() { final String pkg = "package"; List<StatusBarNotification> posted = new ArrayList<>(); for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) { @@ -427,7 +621,8 @@ public class GroupHelperTest extends UiServiceTestCase { posted.add(sbn); mGroupHelper.onNotificationPosted(sbn, false); } - verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(), eq(false)); + verify(mCallback, times(1)).addAutoGroupSummary( + anyInt(), eq(pkg), anyString(), eq(BASE_FLAGS)); verify(mCallback, times(AUTOGROUP_AT_COUNT)).addAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); @@ -441,10 +636,7 @@ public class GroupHelperTest extends UiServiceTestCase { Mockito.reset(mCallback); // only one child remains - Map<String, LinkedHashSet<String>> ungroupedForUser = - mGroupHelper.mUngroupedNotifications.get(UserHandle.USER_SYSTEM); - assertNotNull(ungroupedForUser); - assertEquals(1, ungroupedForUser.get(pkg).size()); + assertEquals(1, mGroupHelper.getNotGroupedByAppCount(UserHandle.USER_SYSTEM, pkg)); // Add new notification; it should be autogrouped even though the total count is // < AUTOGROUP_AT_COUNT @@ -454,5 +646,8 @@ public class GroupHelperTest extends UiServiceTestCase { verify(mCallback, times(1)).addAutoGroup(sbn.getKey()); verify(mCallback, never()).removeAutoGroup(anyString()); verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString()); + verify(mCallback).updateAutogroupSummary(anyInt(), anyString(), eq(BASE_FLAGS)); + verify(mCallback, never()).addAutoGroupSummary( + anyInt(), anyString(), anyString(), anyInt()); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index eceb589bba76..9cfdaa7cad0c 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -754,13 +754,19 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id, String groupKey, boolean isSummary) { + return generateNotificationRecord(channel, id, "tag" + System.currentTimeMillis(), groupKey, + isSummary); + } + + private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id, + String tag, String groupKey, boolean isSummary) { Notification.Builder nb = new Notification.Builder(mContext, channel.getId()) .setContentTitle("foo") .setSmallIcon(android.R.drawable.sym_def_app_icon) .setGroup(groupKey) .setGroupSummary(isSummary); StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id, - "tag" + System.currentTimeMillis(), mUid, 0, + tag, mUid, 0, nb.build(), UserHandle.getUserHandleForUid(mUid), null, 0); return new NotificationRecord(mContext, sbn, channel); } @@ -1899,7 +1905,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.mSummaryByGroupKey.put("pkg", summary); mService.mAutobundledSummaries.put(0, new ArrayMap<>()); mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey()); - mService.updateAutobundledSummaryFlags(0, "pkg", true, false); + mService.updateAutobundledSummaryFlags( + 0, "pkg", GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT, false); assertTrue(summary.getSbn().isOngoing()); } @@ -1915,7 +1922,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mService.mAutobundledSummaries.get(0).put("pkg", summary.getKey()); mService.mSummaryByGroupKey.put("pkg", summary); - mService.updateAutobundledSummaryFlags(0, "pkg", false, false); + mService.updateAutobundledSummaryFlags(0, "pkg", GroupHelper.BASE_FLAGS, false); assertFalse(summary.getSbn().isOngoing()); } @@ -2897,7 +2904,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mPermissionHelper.isPermissionFixed(PKG, temp.getUserId())).thenReturn(true); NotificationRecord r = mService.createAutoGroupSummary( - temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), false); + temp.getUserId(), temp.getSbn().getPackageName(), temp.getKey(), 0); assertThat(r.isImportanceFixed()).isTrue(); } @@ -4213,7 +4220,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testOnlyAutogroupIfGroupChanged_noPriorNoti_autogroups() throws Exception { + public void testOnlyAutogroupIfNeeded_newNotification_ghUpdate() { NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, null, false); mService.addEnqueuedNotification(r); NotificationManagerService.PostNotificationRunnable runnable = @@ -4226,17 +4233,18 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testOnlyAutogroupIfGroupChanged_groupChanged_autogroups() - throws Exception { - NotificationRecord r = - generateNotificationRecord(mTestNotificationChannel, 0, "group", false); + public void testOnlyAutogroupIfNeeded_groupChanged_ghUpdate() { + NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, + "testOnlyAutogroupIfNeeded_groupChanged_ghUpdate", "group", false); mService.addNotification(r); - r = generateNotificationRecord(mTestNotificationChannel, 0, null, false); - mService.addEnqueuedNotification(r); + NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0, + "testOnlyAutogroupIfNeeded_groupChanged_ghUpdate", null, false); + mService.addEnqueuedNotification(update); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), SystemClock.elapsedRealtime()); + mService.new PostNotificationRunnable(update.getKey(), + update.getSbn().getPackageName(), update.getUid(), + SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -4244,16 +4252,39 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testOnlyAutogroupIfGroupChanged_noGroupChanged_autogroups() - throws Exception { - NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, "group", - false); + public void testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate() { + NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, + "testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate", "group", false); mService.addNotification(r); - mService.addEnqueuedNotification(r); + NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0, + "testOnlyAutogroupIfNeeded_flagsChanged_ghUpdate", null, false); + update.getNotification().flags = FLAG_AUTO_CANCEL; + mService.addEnqueuedNotification(update); NotificationManagerService.PostNotificationRunnable runnable = - mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), - r.getUid(), SystemClock.elapsedRealtime()); + mService.new PostNotificationRunnable(update.getKey(), + update.getSbn().getPackageName(), update.getUid(), + SystemClock.elapsedRealtime()); + runnable.run(); + waitForIdle(); + + verify(mGroupHelper, times(1)).onNotificationPosted(any(), anyBoolean()); + } + + @Test + public void testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate() { + NotificationRecord r = generateNotificationRecord(mTestNotificationChannel, 0, + "testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false); + mService.addNotification(r); + NotificationRecord update = generateNotificationRecord(mTestNotificationChannel, 0, + "testOnlyAutogroupIfGroupChanged_noValidChange_noGhUpdate", null, false); + update.getNotification().color = Color.BLACK; + mService.addEnqueuedNotification(update); + + NotificationManagerService.PostNotificationRunnable runnable = + mService.new PostNotificationRunnable(update.getKey(), + update.getSbn().getPackageName(), + update.getUid(), SystemClock.elapsedRealtime()); runnable.run(); waitForIdle(); @@ -10220,10 +10251,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // grouphelper is a mock here, so make the calls it would make - // add summary; wait for it to be posted - mService.addAutoGroupSummary(nr1.getUserId(), nr1.getSbn().getPackageName(), nr1.getKey(), - true); - waitForIdle(); + // add summary + mService.addNotification(mService.createAutoGroupSummary(nr1.getUserId(), + nr1.getSbn().getPackageName(), nr1.getKey(), + GroupHelper.BASE_FLAGS | FLAG_ONGOING_EVENT)); // cancel both children mBinderService.cancelNotificationWithTag(PKG, PKG, nr0.getSbn().getTag(), @@ -10232,9 +10263,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { nr1.getSbn().getId(), nr1.getSbn().getUserId()); waitForIdle(); - // group helper would send 'remove flag' and then 'remove summary' events - mService.updateAutobundledSummaryFlags(nr1.getUserId(), nr1.getSbn().getPackageName(), - false, false); + // group helper would send 'remove summary' event mService.clearAutogroupSummaryLocked(nr1.getUserId(), nr1.getSbn().getPackageName()); waitForIdle(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java index e6569f7e0ce2..9fe0e49c4ab8 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordExtractorDataTest.java @@ -98,6 +98,41 @@ public class NotificationRecordExtractorDataTest extends UiServiceTestCase { } @Test + public void testHasDiffs_autoBundled() { + NotificationRecord r = generateRecord(); + + NotificationRecordExtractorData extractorData = new NotificationRecordExtractorData( + 1, + r.getPackageVisibilityOverride(), + r.canShowBadge(), + r.canBubble(), + r.getNotification().isBubbleNotification(), + r.getChannel(), + r.getGroupKey(), + r.getPeopleOverride(), + r.getSnoozeCriteria(), + r.getUserSentiment(), + r.getSuppressedVisualEffects(), + r.getSystemGeneratedSmartActions(), + r.getSmartReplies(), + r.getImportance(), + r.getRankingScore(), + r.isConversation(), + r.getProposedImportance(), + r.hasSensitiveContent()); + + Bundle signals = new Bundle(); + signals.putString(Adjustment.KEY_GROUP_KEY, "ranker_group"); + Adjustment adjustment = new Adjustment("pkg", r.getKey(), signals, "", 0); + r.addAdjustment(adjustment); + NotificationAdjustmentExtractor adjustmentExtractor = new NotificationAdjustmentExtractor(); + adjustmentExtractor.process(r); + + assertTrue(extractorData.hasDiffForRankingLocked(r, 1)); + assertTrue(extractorData.hasDiffForLoggingLocked(r, 1)); + } + + @Test public void testHasDiffs_sensitiveContentChange() { NotificationRecord r = generateRecord(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java new file mode 100644 index 000000000000..bcd807ab6d2f --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeDiffTest.java @@ -0,0 +1,319 @@ +/* + * 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.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; + +import android.content.ComponentName; +import android.net.Uri; +import android.provider.Settings; +import android.service.notification.Condition; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeDiff; +import android.service.notification.ZenPolicy; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.util.ArrayMap; + +import androidx.test.filters.SmallTest; + +import com.android.server.UiServiceTestCase; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class ZenModeDiffTest extends UiServiceTestCase { + // version is not included in the diff; manual & automatic rules have special handling + public static final Set<String> ZEN_MODE_CONFIG_EXEMPT_FIELDS = + Set.of("version", "manualRule", "automaticRules"); + + @Test + public void testRuleDiff_addRemoveSame() { + // Test add, remove, and both sides same + ZenModeConfig.ZenRule r = makeRule(); + + // Both sides same rule + ZenModeDiff.RuleDiff dSame = new ZenModeDiff.RuleDiff(r, r); + assertFalse(dSame.hasDiff()); + + // from existent rule to null: expect deleted + ZenModeDiff.RuleDiff deleted = new ZenModeDiff.RuleDiff(r, null); + assertTrue(deleted.hasDiff()); + assertTrue(deleted.wasRemoved()); + + // from null to new rule: expect added + ZenModeDiff.RuleDiff added = new ZenModeDiff.RuleDiff(null, r); + assertTrue(added.hasDiff()); + assertTrue(added.wasAdded()); + } + + @Test + public void testRuleDiff_fieldDiffs() throws Exception { + // Start these the same + ZenModeConfig.ZenRule r1 = makeRule(); + ZenModeConfig.ZenRule r2 = makeRule(); + + // maps mapping field name -> expected output value as we set diffs + ArrayMap<String, Object> expectedFrom = new ArrayMap<>(); + ArrayMap<String, Object> expectedTo = new ArrayMap<>(); + List<Field> fieldsForDiff = getFieldsForDiffCheck( + ZenModeConfig.ZenRule.class, Set.of()); // actually no exempt fields for ZenRule + generateFieldDiffs(r1, r2, fieldsForDiff, expectedFrom, expectedTo); + + ZenModeDiff.RuleDiff d = new ZenModeDiff.RuleDiff(r1, r2); + assertTrue(d.hasDiff()); + + // Now diff them and check that each of the fields has a diff + for (Field f : fieldsForDiff) { + String name = f.getName(); + assertNotNull("diff not found for field: " + name, d.getDiffForField(name)); + assertTrue(d.getDiffForField(name).hasDiff()); + assertTrue("unexpected field: " + name, expectedFrom.containsKey(name)); + assertTrue("unexpected field: " + name, expectedTo.containsKey(name)); + assertEquals(expectedFrom.get(name), d.getDiffForField(name).from()); + assertEquals(expectedTo.get(name), d.getDiffForField(name).to()); + } + } + + @Test + public void testConfigDiff_addRemoveSame() { + // Default config, will test add, remove, and no change + ZenModeConfig c = new ZenModeConfig(); + + ZenModeDiff.ConfigDiff dSame = new ZenModeDiff.ConfigDiff(c, c); + assertFalse(dSame.hasDiff()); + + ZenModeDiff.ConfigDiff added = new ZenModeDiff.ConfigDiff(null, c); + assertTrue(added.hasDiff()); + assertTrue(added.wasAdded()); + + ZenModeDiff.ConfigDiff removed = new ZenModeDiff.ConfigDiff(c, null); + assertTrue(removed.hasDiff()); + assertTrue(removed.wasRemoved()); + } + + @Test + public void testConfigDiff_fieldDiffs() throws Exception { + // these two start the same + ZenModeConfig c1 = new ZenModeConfig(); + ZenModeConfig c2 = new ZenModeConfig(); + + // maps mapping field name -> expected output value as we set diffs + ArrayMap<String, Object> expectedFrom = new ArrayMap<>(); + ArrayMap<String, Object> expectedTo = new ArrayMap<>(); + List<Field> fieldsForDiff = getFieldsForDiffCheck( + ZenModeConfig.class, ZEN_MODE_CONFIG_EXEMPT_FIELDS); + generateFieldDiffs(c1, c2, fieldsForDiff, expectedFrom, expectedTo); + + ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2); + assertTrue(d.hasDiff()); + + // Now diff them and check that each of the fields has a diff + for (Field f : fieldsForDiff) { + String name = f.getName(); + assertNotNull("diff not found for field: " + name, d.getDiffForField(name)); + assertTrue(d.getDiffForField(name).hasDiff()); + assertTrue("unexpected field: " + name, expectedFrom.containsKey(name)); + assertTrue("unexpected field: " + name, expectedTo.containsKey(name)); + assertEquals(expectedFrom.get(name), d.getDiffForField(name).from()); + assertEquals(expectedTo.get(name), d.getDiffForField(name).to()); + } + } + + @Test + public void testConfigDiff_specialSenders() { + // these two start the same + ZenModeConfig c1 = new ZenModeConfig(); + ZenModeConfig c2 = new ZenModeConfig(); + + // set c1 and c2 to have some different senders + c1.allowMessagesFrom = ZenModeConfig.SOURCE_STAR; + c2.allowMessagesFrom = ZenModeConfig.SOURCE_CONTACT; + c1.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_IMPORTANT; + c2.allowConversationsFrom = ZenPolicy.CONVERSATION_SENDERS_NONE; + + ZenModeDiff.ConfigDiff d = new ZenModeDiff.ConfigDiff(c1, c2); + assertTrue(d.hasDiff()); + + // Diff in top-level fields + assertTrue(d.getDiffForField("allowMessagesFrom").hasDiff()); + assertTrue(d.getDiffForField("allowConversationsFrom").hasDiff()); + + // Bonus testing of stringification of people senders and conversation senders + assertTrue(d.toString().contains("allowMessagesFrom:stars->contacts")); + assertTrue(d.toString().contains("allowConversationsFrom:important->none")); + } + + @Test + public void testConfigDiff_hasRuleDiffs() { + // two default configs + ZenModeConfig c1 = new ZenModeConfig(); + ZenModeConfig c2 = new ZenModeConfig(); + + // two initially-identical rules + ZenModeConfig.ZenRule r1 = makeRule(); + ZenModeConfig.ZenRule r2 = makeRule(); + + // one that will become a manual rule + ZenModeConfig.ZenRule m = makeRule(); + + // Add r1 to c1, but not r2 to c2 yet -- expect a rule to be deleted + c1.automaticRules.put(r1.id, r1); + ZenModeDiff.ConfigDiff deleteRule = new ZenModeDiff.ConfigDiff(c1, c2); + assertTrue(deleteRule.hasDiff()); + assertNotNull(deleteRule.getAllAutomaticRuleDiffs()); + assertTrue(deleteRule.getAllAutomaticRuleDiffs().containsKey("ruleId")); + assertTrue(deleteRule.getAllAutomaticRuleDiffs().get("ruleId").wasRemoved()); + + // Change r2 a little, add r2 to c2 as an automatic rule and m as a manual rule + r2.component = null; + r2.pkg = "different"; + c2.manualRule = m; + c2.automaticRules.put(r2.id, r2); + + // Expect diffs in both manual rule (added) and automatic rule (changed) + ZenModeDiff.ConfigDiff changed = new ZenModeDiff.ConfigDiff(c1, c2); + assertTrue(changed.hasDiff()); + assertTrue(changed.getManualRuleDiff().hasDiff()); + + ArrayMap<String, ZenModeDiff.RuleDiff> automaticDiffs = changed.getAllAutomaticRuleDiffs(); + assertNotNull(automaticDiffs); + assertTrue(automaticDiffs.containsKey("ruleId")); + assertNotNull(automaticDiffs.get("ruleId").getDiffForField("component")); + assertNull(automaticDiffs.get("ruleId").getDiffForField("component").to()); + assertNotNull(automaticDiffs.get("ruleId").getDiffForField("pkg")); + assertEquals("different", automaticDiffs.get("ruleId").getDiffForField("pkg").to()); + } + + // Helper methods for working with configs, policies, rules + // Just makes a zen rule with fields filled in + private ZenModeConfig.ZenRule makeRule() { + ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); + rule.configurationActivity = new ComponentName("a", "a"); + rule.component = new ComponentName("b", "b"); + rule.conditionId = new Uri.Builder().scheme("hello").build(); + rule.condition = new Condition(rule.conditionId, "", Condition.STATE_TRUE); + rule.enabled = true; + rule.creationTime = 123; + rule.id = "ruleId"; + rule.zenMode = Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; + rule.modified = false; + rule.name = "name"; + rule.snoozing = true; + rule.pkg = "a"; + return rule; + } + + // Get the fields on which we would want to check a diff. The requirements are: not final or/ + // static (as these should/can never change), and not in a specific list that's exempted. + private List<Field> getFieldsForDiffCheck(Class c, Set<String> exemptNames) + throws SecurityException { + Field[] fields = c.getDeclaredFields(); + ArrayList<Field> out = new ArrayList<>(); + + for (Field field : fields) { + // Check for exempt reasons + int m = field.getModifiers(); + if (Modifier.isFinal(m) + || Modifier.isStatic(m) + || exemptNames.contains(field.getName())) { + continue; + } + out.add(field); + } + return out; + } + + // Generate a set of generic diffs for the specified two objects and the fields to generate + // diffs for, and store the results in the provided expectation maps to be able to check the + // output later. + private void generateFieldDiffs(Object a, Object b, List<Field> fields, + ArrayMap<String, Object> expectedA, ArrayMap<String, Object> expectedB) + throws Exception { + // different classes passed in means bad input + assertEquals(a.getClass(), b.getClass()); + + // Loop through fields for which we want to check diffs, set a diff and keep track of + // what we set. + for (Field f : fields) { + f.setAccessible(true); + // Just double-check also that the fields actually are for the class declared + assertEquals(f.getDeclaringClass(), a.getClass()); + Class t = f.getType(); + // handle the full set of primitive types first + if (boolean.class.equals(t)) { + f.setBoolean(a, true); + expectedA.put(f.getName(), true); + f.setBoolean(b, false); + expectedB.put(f.getName(), false); + } else if (int.class.equals(t)) { + // these are not actually valid going to be valid for arbitrary int enum fields, but + // we just put something in there regardless. + f.setInt(a, 2); + expectedA.put(f.getName(), 2); + f.setInt(b, 1); + expectedB.put(f.getName(), 1); + } else if (long.class.equals(t)) { + f.setLong(a, 200L); + expectedA.put(f.getName(), 200L); + f.setLong(b, 100L); + expectedB.put(f.getName(), 100L); + } else if (t.isPrimitive()) { + // This method doesn't yet handle other primitive types. If the relevant diff + // classes gain new fields of these types, please add another clause here. + fail("primitive type not handled by generateFieldDiffs: " + t.getName()); + } else if (String.class.equals(t)) { + f.set(a, "string1"); + expectedA.put(f.getName(), "string1"); + f.set(b, "string2"); + expectedB.put(f.getName(), "string2"); + } else { + // catch-all for other types: have the field be "added" + f.set(a, null); + expectedA.put(f.getName(), null); + try { + f.set(b, t.getDeclaredConstructor().newInstance()); + expectedB.put(f.getName(), t.getDeclaredConstructor().newInstance()); + } catch (Exception e) { + // No default constructor, or blithely attempting to construct something doesn't + // work for some reason. If the default value isn't null, then keep it. + if (f.get(b) != null) { + expectedB.put(f.getName(), f.get(b)); + } else { + // If we can't even rely on that, fail. Have the test-writer special case + // something, as this is not able to be genericized. + fail("could not generically construct value for field: " + f.getName()); + } + } + } + } + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index 6f9798ea7d69..b2a54010e75e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -69,8 +69,6 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AutomaticZenRule; @@ -78,7 +76,6 @@ import android.app.NotificationManager; import android.app.NotificationManager.Policy; import android.content.ComponentName; import android.content.ContentResolver; -import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -90,7 +87,6 @@ import android.media.AudioManagerInternal; import android.media.AudioSystem; import android.media.VolumePolicy; import android.net.Uri; -import android.os.Binder; import android.os.Process; import android.os.UserHandle; import android.provider.Settings; @@ -98,6 +94,7 @@ import android.provider.Settings.Global; import android.service.notification.Condition; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ScheduleInfo; +import android.service.notification.ZenModeDiff; import android.service.notification.ZenPolicy; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; @@ -877,7 +874,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelperSpy.readXml(parser, false, UserHandle.USER_ALL); assertEquals("Config mismatch: current vs expected: " - + mZenModeHelperSpy.mConfig.diff(expected), expected, mZenModeHelperSpy.mConfig); + + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, expected), expected, + mZenModeHelperSpy.mConfig); } @Test @@ -1046,7 +1044,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10); assertEquals( - "Config mismatch: current vs expected: " + actual.diff(config10), config10, actual); + "Config mismatch: current vs expected: " + + new ZenModeDiff.ConfigDiff(actual, config10), config10, actual); assertNotEquals("Expected config mismatch", config11, mZenModeHelperSpy.mConfigs.get(11)); } @@ -1062,7 +1061,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelperSpy.readXml(parser, true, UserHandle.USER_SYSTEM); assertEquals("Config mismatch: current vs original: " - + mZenModeHelperSpy.mConfig.diff(original), original, mZenModeHelperSpy.mConfig); + + new ZenModeDiff.ConfigDiff(mZenModeHelperSpy.mConfig, original), + original, mZenModeHelperSpy.mConfig); assertEquals(original.hashCode(), mZenModeHelperSpy.mConfig.hashCode()); } @@ -1083,8 +1083,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig actual = mZenModeHelperSpy.mConfigs.get(10); expected.user = 10; - assertEquals( - "Config mismatch: current vs original: " + actual.diff(expected), expected, actual); + assertEquals("Config mismatch: current vs original: " + + new ZenModeDiff.ConfigDiff(actual, expected), + expected, actual); assertEquals(expected.hashCode(), actual.hashCode()); expected.user = 0; assertNotEquals(expected, mZenModeHelperSpy.mConfig); diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp index 986fb71afa2d..e704ebf32270 100644 --- a/services/tests/voiceinteractiontests/Android.bp +++ b/services/tests/voiceinteractiontests/Android.bp @@ -40,6 +40,7 @@ android_test { "platform-test-annotations", "services.core", "services.voiceinteraction", + "services.soundtrigger", "servicestests-core-utils", "servicestests-utils-mockito-extended", "truth-prebuilt", 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/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index c131c84a50ce..7092b0b5ac34 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -104,7 +104,6 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { when(mMockRunner.asBinder()).thenReturn(new Binder()); mController = spy(new RecentsAnimationController(mWm, mMockRunner, mAnimationCallbacks, DEFAULT_DISPLAY)); - mController.mShouldAttachNavBarToAppDuringTransition = false; mRootHomeTask = mDefaultDisplay.getDefaultTaskDisplayArea().getRootHomeTask(); assertNotNull(mRootHomeTask); } @@ -814,13 +813,13 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { } private void setupForShouldAttachNavBarDuringTransition() { - mController.mShouldAttachNavBarToAppDuringTransition = true; final WindowState navBar = spy(createWindow(null, TYPE_NAVIGATION_BAR, "NavigationBar")); mDefaultDisplay.getDisplayPolicy().addWindowLw(navBar, navBar.mAttrs); mWm.setRecentsAnimationController(mController); doReturn(navBar).when(mController).getNavigationBarWindow(); final DisplayPolicy displayPolicy = spy(mDefaultDisplay.getDisplayPolicy()); doReturn(displayPolicy).when(mDefaultDisplay).getDisplayPolicy(); + doReturn(true).when(displayPolicy).shouldAttachNavBarToAppDuringTransition(); } private static void initializeRecentsAnimationController(RecentsAnimationController controller, 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/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java index 6cf2b2d7a31e..74ba45c130e3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java @@ -500,7 +500,6 @@ public class ZOrderingTests extends WindowTestsBase { RecentsAnimationController controller = new RecentsAnimationController( mWm, mockRunner, null, displayId); spyOn(controller); - controller.mShouldAttachNavBarToAppDuringTransition = true; doReturn(mNavBarWindow).when(controller).getNavigationBarWindow(); mWm.setRecentsAnimationController(controller); @@ -508,6 +507,10 @@ public class ZOrderingTests extends WindowTestsBase { spyOn(mDisplayContent.mInputMethodWindow); doReturn(true).when(mDisplayContent.mInputMethodWindow).isVisible(); + DisplayPolicy policy = mDisplayContent.getDisplayPolicy(); + spyOn(policy); + doReturn(true).when(policy).shouldAttachNavBarToAppDuringTransition(); + // create home activity Task rootHomeTask = mDisplayContent.getDefaultTaskDisplayArea().getRootHomeTask(); final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService) diff --git a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java index 337e1f92050c..7fe8582f96de 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaDevice.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaDevice.java @@ -27,6 +27,8 @@ import android.util.Slog; import com.android.internal.util.dump.DualDumpOutputStream; import com.android.server.audio.AudioService; +import java.util.Arrays; + /** * Represents the ALSA specification, and attributes of an ALSA device. */ @@ -36,17 +38,21 @@ public final class UsbAlsaDevice { private final int mCardNum; private final int mDeviceNum; + private final String mAlsaCardDeviceString; private final String mDeviceAddress; - private final boolean mHasOutput; - private final boolean mHasInput; - private final boolean mIsInputHeadset; - private final boolean mIsOutputHeadset; - private final boolean mIsDock; + // The following two constant will be used as index to access arrays. + private static final int INPUT = 0; + private static final int OUTPUT = 1; + private static final int NUM_DIRECTIONS = 2; + private static final String[] DIRECTION_STR = {"INPUT", "OUTPUT"}; + private final boolean[] mHasDevice = new boolean[NUM_DIRECTIONS]; - private boolean mSelected = false; - private int mOutputState; - private int mInputState; + private final boolean[] mIsHeadset = new boolean[NUM_DIRECTIONS]; + private final boolean mIsDock; + private final int[] mDeviceType = new int[NUM_DIRECTIONS]; + private boolean[] mIsSelected = new boolean[NUM_DIRECTIONS]; + private int[] mState = new int[NUM_DIRECTIONS]; private UsbAlsaJackDetector mJackDetector; private IAudioService mAudioService; @@ -60,11 +66,13 @@ public final class UsbAlsaDevice { mCardNum = card; mDeviceNum = device; mDeviceAddress = deviceAddress; - mHasOutput = hasOutput; - mHasInput = hasInput; - mIsInputHeadset = isInputHeadset; - mIsOutputHeadset = isOutputHeadset; + mHasDevice[OUTPUT] = hasOutput; + mHasDevice[INPUT] = hasInput; + mIsHeadset[INPUT] = isInputHeadset; + mIsHeadset[OUTPUT] = isOutputHeadset; mIsDock = isDock; + initDeviceType(); + mAlsaCardDeviceString = getAlsaCardDeviceString(); } /** @@ -104,28 +112,28 @@ public final class UsbAlsaDevice { * @return true if the device supports output. */ public boolean hasOutput() { - return mHasOutput; + return mHasDevice[OUTPUT]; } /** * @return true if the device supports input (recording). */ public boolean hasInput() { - return mHasInput; + return mHasDevice[INPUT]; } /** - * @return true if the device is a headset for purposes of input. + * @return true if the device is a headset for purposes of output. */ - public boolean isInputHeadset() { - return mIsInputHeadset; + public boolean isOutputHeadset() { + return mIsHeadset[OUTPUT]; } /** - * @return true if the device is a headset for purposes of output. + * @return true if the device is a headset for purposes of input. */ - public boolean isOutputHeadset() { - return mIsOutputHeadset; + public boolean isInputHeadset() { + return mIsHeadset[INPUT]; } /** @@ -157,6 +165,9 @@ public final class UsbAlsaDevice { /** Begins a jack-detection thread. */ private synchronized void startJackDetect() { + if (mJackDetector != null) { + return; + } // If no jack detect capabilities exist, mJackDetector will be null. mJackDetector = UsbAlsaJackDetector.startJackDetect(this); } @@ -171,75 +182,152 @@ public final class UsbAlsaDevice { /** Start using this device as the selected USB Audio Device. */ public synchronized void start() { - mSelected = true; - mInputState = 0; - mOutputState = 0; + startInput(); + startOutput(); + } + + /** Start using this device as the selected USB input device. */ + public synchronized void startInput() { + startDevice(INPUT); + } + + /** Start using this device as selected USB output device. */ + public synchronized void startOutput() { + startDevice(OUTPUT); + } + + private void startDevice(int direction) { + if (mIsSelected[direction]) { + return; + } + mIsSelected[direction] = true; + mState[direction] = 0; startJackDetect(); - updateWiredDeviceConnectionState(true); + updateWiredDeviceConnectionState(direction, true /*enable*/); } /** Stop using this device as the selected USB Audio Device. */ public synchronized void stop() { - stopJackDetect(); - updateWiredDeviceConnectionState(false); - mSelected = false; + stopInput(); + stopOutput(); } - /** Updates AudioService with the connection state of the alsaDevice. - * Checks ALSA Jack state for inputs and outputs before reporting. - */ - public synchronized void updateWiredDeviceConnectionState(boolean enable) { - if (!mSelected) { - Slog.e(TAG, "updateWiredDeviceConnectionState on unselected AlsaDevice!"); + /** Stop using this device as the selected USB input device. */ + public synchronized void stopInput() { + if (!mIsSelected[INPUT]) { return; } - String alsaCardDeviceString = getAlsaCardDeviceString(); - if (alsaCardDeviceString == null) { + if (!mIsSelected[OUTPUT]) { + // Stop jack detection when both input and output are stopped + stopJackDetect(); + } + updateInputWiredDeviceConnectionState(false /*enable*/); + mIsSelected[INPUT] = false; + } + + /** Stop using this device as the selected USB output device. */ + public synchronized void stopOutput() { + if (!mIsSelected[OUTPUT]) { return; } - try { - // Output Device - if (mHasOutput) { - int device = mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET - : (mIsOutputHeadset - ? AudioSystem.DEVICE_OUT_USB_HEADSET - : AudioSystem.DEVICE_OUT_USB_DEVICE); - if (DEBUG) { - Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(device) - + " addr:" + alsaCardDeviceString - + " name:" + mDeviceName); - } - boolean connected = isOutputJackConnected(); - Slog.i(TAG, "OUTPUT JACK connected: " + connected); - int outputState = (enable && connected) ? 1 : 0; - if (outputState != mOutputState) { - mOutputState = outputState; - AudioDeviceAttributes attributes = new AudioDeviceAttributes(device, - alsaCardDeviceString, mDeviceName); - mAudioService.setWiredDeviceConnectionState(attributes, outputState, TAG); - } - } + if (!mIsSelected[INPUT]) { + // Stop jack detection when both input and output are stopped + stopJackDetect(); + } + updateOutputWiredDeviceConnectionState(false /*enable*/); + mIsSelected[OUTPUT] = false; + } + + private void initDeviceType() { + mDeviceType[INPUT] = mHasDevice[INPUT] + ? (mIsHeadset[INPUT] ? AudioSystem.DEVICE_IN_USB_HEADSET + : AudioSystem.DEVICE_IN_USB_DEVICE) + : AudioSystem.DEVICE_NONE; + mDeviceType[OUTPUT] = mHasDevice[OUTPUT] + ? (mIsDock ? AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET + : (mIsHeadset[OUTPUT] ? AudioSystem.DEVICE_OUT_USB_HEADSET + : AudioSystem.DEVICE_OUT_USB_DEVICE)) + : AudioSystem.DEVICE_NONE; + } - // Input Device - if (mHasInput) { - int device = mIsInputHeadset - ? AudioSystem.DEVICE_IN_USB_HEADSET - : AudioSystem.DEVICE_IN_USB_DEVICE; - boolean connected = isInputJackConnected(); - Slog.i(TAG, "INPUT JACK connected: " + connected); - int inputState = (enable && connected) ? 1 : 0; - if (inputState != mInputState) { - mInputState = inputState; - AudioDeviceAttributes attributes = new AudioDeviceAttributes(device, - alsaCardDeviceString, mDeviceName); - mAudioService.setWiredDeviceConnectionState(attributes, inputState, TAG); - } + /** + * @return the output device type that will be used to notify AudioService about device + * connection. If there is no output on this device, {@link AudioSystem#DEVICE_NONE} + * will be returned. + */ + public int getOutputDeviceType() { + return mDeviceType[OUTPUT]; + } + + /** + * @return the input device type that will be used to notify AudioService about device + * connection. If there is no input on this device, {@link AudioSystem#DEVICE_NONE} + * will be returned. + */ + public int getInputDeviceType() { + return mDeviceType[INPUT]; + } + + private boolean updateWiredDeviceConnectionState(int direction, boolean enable) { + if (!mIsSelected[direction]) { + Slog.e(TAG, "Updating wired device connection state on unselected device"); + return false; + } + if (mDeviceType[direction] == AudioSystem.DEVICE_NONE) { + Slog.d(TAG, + "Unable to set device connection state as " + DIRECTION_STR[direction] + + " device type is none"); + return false; + } + if (mAlsaCardDeviceString == null) { + Slog.w(TAG, "Failed to update " + DIRECTION_STR[direction] + " device connection " + + "state failed as alsa card device string is null"); + return false; + } + if (DEBUG) { + Slog.d(TAG, "pre-call device:0x" + Integer.toHexString(mDeviceType[direction]) + + " addr:" + mAlsaCardDeviceString + + " name:" + mDeviceName); + } + boolean connected = direction == INPUT ? isInputJackConnected() : isOutputJackConnected(); + Slog.i(TAG, DIRECTION_STR[direction] + " JACK connected: " + connected); + int state = (enable && connected) ? 1 : 0; + if (state != mState[direction]) { + mState[direction] = state; + AudioDeviceAttributes attributes = new AudioDeviceAttributes( + mDeviceType[direction], mAlsaCardDeviceString, mDeviceName); + try { + mAudioService.setWiredDeviceConnectionState(attributes, state, TAG); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState for " + + DIRECTION_STR[direction]); + return false; } - } catch (RemoteException e) { - Slog.e(TAG, "RemoteException in setWiredDeviceConnectionState"); } + return true; } + /** + * Notify AudioService about the input device connection state. + * + * @param enable true to notify the device as connected. + * @return true only when it successfully notifies AudioService about the device + * connection state. + */ + public synchronized boolean updateInputWiredDeviceConnectionState(boolean enable) { + return updateWiredDeviceConnectionState(INPUT, enable); + } + + /** + * Notify AudioService about the output device connection state. + * + * @param enable true to notify the device as connected. + * @return true only when it successfully notifies AudioService about the device + * connection state. + */ + public synchronized boolean updateOutputWiredDeviceConnectionState(boolean enable) { + return updateWiredDeviceConnectionState(OUTPUT, enable); + } /** * @Override @@ -249,8 +337,8 @@ public final class UsbAlsaDevice { return "UsbAlsaDevice: [card: " + mCardNum + ", device: " + mDeviceNum + ", name: " + mDeviceName - + ", hasOutput: " + mHasOutput - + ", hasInput: " + mHasInput + "]"; + + ", hasOutput: " + mHasDevice[OUTPUT] + + ", hasInput: " + mHasDevice[INPUT] + "]"; } /** @@ -262,8 +350,8 @@ public final class UsbAlsaDevice { dump.write("card", UsbAlsaDeviceProto.CARD, mCardNum); dump.write("device", UsbAlsaDeviceProto.DEVICE, mDeviceNum); dump.write("name", UsbAlsaDeviceProto.NAME, mDeviceName); - dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasOutput); - dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasInput); + dump.write("has_output", UsbAlsaDeviceProto.HAS_PLAYBACK, mHasDevice[OUTPUT]); + dump.write("has_input", UsbAlsaDeviceProto.HAS_CAPTURE, mHasDevice[INPUT]); dump.write("address", UsbAlsaDeviceProto.ADDRESS, mDeviceAddress); dump.end(token); @@ -294,10 +382,8 @@ public final class UsbAlsaDevice { UsbAlsaDevice other = (UsbAlsaDevice) obj; return (mCardNum == other.mCardNum && mDeviceNum == other.mDeviceNum - && mHasOutput == other.mHasOutput - && mHasInput == other.mHasInput - && mIsInputHeadset == other.mIsInputHeadset - && mIsOutputHeadset == other.mIsOutputHeadset + && Arrays.equals(mHasDevice, other.mHasDevice) + && Arrays.equals(mIsHeadset, other.mIsHeadset) && mIsDock == other.mIsDock); } @@ -310,10 +396,10 @@ public final class UsbAlsaDevice { int result = 1; result = prime * result + mCardNum; result = prime * result + mDeviceNum; - result = prime * result + (mHasOutput ? 0 : 1); - result = prime * result + (mHasInput ? 0 : 1); - result = prime * result + (mIsInputHeadset ? 0 : 1); - result = prime * result + (mIsOutputHeadset ? 0 : 1); + result = prime * result + (mHasDevice[OUTPUT] ? 0 : 1); + result = prime * result + (mHasDevice[INPUT] ? 0 : 1); + result = prime * result + (mIsHeadset[INPUT] ? 0 : 1); + result = prime * result + (mIsHeadset[OUTPUT] ? 0 : 1); result = prime * result + (mIsDock ? 0 : 1); return result; diff --git a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java index c4988478df71..d4f0b59dd7f2 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaJackDetector.java @@ -81,7 +81,8 @@ public final class UsbAlsaJackDetector implements Runnable { if (mStopJackDetect) { return false; } - mAlsaDevice.updateWiredDeviceConnectionState(true); + mAlsaDevice.updateOutputWiredDeviceConnectionState(true); + mAlsaDevice.updateInputWiredDeviceConnectionState(true); } return true; } diff --git a/services/usb/java/com/android/server/usb/UsbAlsaManager.java b/services/usb/java/com/android/server/usb/UsbAlsaManager.java index aa1d556d02d3..99881e194b07 100644 --- a/services/usb/java/com/android/server/usb/UsbAlsaManager.java +++ b/services/usb/java/com/android/server/usb/UsbAlsaManager.java @@ -20,12 +20,14 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Resources; import android.hardware.usb.UsbDevice; +import android.media.AudioManager; import android.media.IAudioService; import android.media.midi.MidiDeviceInfo; import android.os.Bundle; import android.os.FileObserver; import android.os.ServiceManager; import android.os.SystemClock; +import android.os.SystemProperties; import android.provider.Settings; import android.service.usb.UsbAlsaManagerProto; import android.util.Slog; @@ -42,6 +44,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Stack; /** * UsbAlsaManager manages USB audio and MIDI devices. @@ -51,8 +54,9 @@ public final class UsbAlsaManager { private static final boolean DEBUG = false; // Flag to turn on/off multi-peripheral select mode - // Set to true to have single-device-only mode - private static final boolean mIsSingleMode = true; + // Set to true to have multi-devices mode + private static final boolean IS_MULTI_MODE = SystemProperties.getBoolean( + "ro.audio.multi_usb_mode", false /*def*/); private static final String ALSA_DIRECTORY = "/dev/snd/"; @@ -70,7 +74,11 @@ public final class UsbAlsaManager { // this is needed to map USB devices to ALSA Audio Devices, especially to remove an // ALSA device when we are notified that its associated USB device has been removed. private final ArrayList<UsbAlsaDevice> mAlsaDevices = new ArrayList<UsbAlsaDevice>(); - private UsbAlsaDevice mSelectedDevice; + // A map from device type to attached devices. Given the audio framework only supports + // single device connection per device type, only the last attached device will be + // connected to audio framework. Once the last device is removed, previous device can + // be connected to audio framework. + private HashMap<Integer, Stack<UsbAlsaDevice>> mAttachedDevices = new HashMap<>(); // // Device Denylist @@ -162,11 +170,6 @@ public final class UsbAlsaManager { Slog.d(TAG, "selectAlsaDevice() " + alsaDevice); } - // This must be where an existing USB audio device is deselected.... (I think) - if (mIsSingleMode && mSelectedDevice != null) { - deselectAlsaDevice(); - } - // FIXME Does not yet handle the case where the setting is changed // after device connection. Ideally we should handle the settings change // in SettingsObserver. Here we should log that a USB device is connected @@ -178,21 +181,18 @@ public final class UsbAlsaManager { return; } - mSelectedDevice = alsaDevice; alsaDevice.start(); + if (DEBUG) { Slog.d(TAG, "selectAlsaDevice() - done."); } } - private synchronized void deselectAlsaDevice() { + private synchronized void deselectAlsaDevice(UsbAlsaDevice selectedDevice) { if (DEBUG) { - Slog.d(TAG, "deselectAlsaDevice() mSelectedDevice " + mSelectedDevice); - } - if (mSelectedDevice != null) { - mSelectedDevice.stop(); - mSelectedDevice = null; + Slog.d(TAG, "deselectAlsaDevice() selectedDevice " + selectedDevice); } + selectedDevice.stop(); } private int getAlsaDeviceListIndexFor(String deviceAddress) { @@ -204,32 +204,86 @@ public final class UsbAlsaManager { return -1; } - private UsbAlsaDevice removeAlsaDeviceFromList(String deviceAddress) { + private void addDeviceToAttachedDevicesMap(int deviceType, UsbAlsaDevice device) { + if (deviceType == AudioManager.DEVICE_NONE) { + Slog.i(TAG, "Ignore caching device as the type is NONE, device=" + device); + return; + } + Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType); + if (devices == null) { + mAttachedDevices.put(deviceType, new Stack<>()); + devices = mAttachedDevices.get(deviceType); + } + devices.push(device); + } + + private void addAlsaDevice(UsbAlsaDevice device) { + mAlsaDevices.add(0, device); + addDeviceToAttachedDevicesMap(device.getInputDeviceType(), device); + addDeviceToAttachedDevicesMap(device.getOutputDeviceType(), device); + } + + private void removeDeviceFromAttachedDevicesMap(int deviceType, UsbAlsaDevice device) { + Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType); + if (devices == null) { + return; + } + devices.remove(device); + if (devices.isEmpty()) { + mAttachedDevices.remove(deviceType); + } + } + + private UsbAlsaDevice removeAlsaDevice(String deviceAddress) { int index = getAlsaDeviceListIndexFor(deviceAddress); if (index > -1) { - return mAlsaDevices.remove(index); + UsbAlsaDevice device = mAlsaDevices.remove(index); + removeDeviceFromAttachedDevicesMap(device.getOutputDeviceType(), device); + removeDeviceFromAttachedDevicesMap(device.getInputDeviceType(), device); + return device; } else { return null; } } - /* package */ UsbAlsaDevice selectDefaultDevice() { + private UsbAlsaDevice selectDefaultDevice(int deviceType) { if (DEBUG) { - Slog.d(TAG, "selectDefaultDevice()"); + Slog.d(TAG, "selectDefaultDevice():" + deviceType); } - if (mAlsaDevices.size() > 0) { - UsbAlsaDevice alsaDevice = mAlsaDevices.get(0); - if (DEBUG) { - Slog.d(TAG, " alsaDevice:" + alsaDevice); - } - if (alsaDevice != null) { - selectAlsaDevice(alsaDevice); - } - return alsaDevice; - } else { + Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType); + if (devices == null || devices.isEmpty()) { return null; } + UsbAlsaDevice alsaDevice = devices.peek(); + Slog.d(TAG, "select default device:" + alsaDevice); + if (AudioManager.isInputDevice(deviceType)) { + alsaDevice.startInput(); + } else { + alsaDevice.startOutput(); + } + return alsaDevice; + } + + private void deselectCurrentDevice(int deviceType) { + if (DEBUG) { + Slog.d(TAG, "deselectCurrentDevice():" + deviceType); + } + if (deviceType == AudioManager.DEVICE_NONE) { + return; + } + + Stack<UsbAlsaDevice> devices = mAttachedDevices.get(deviceType); + if (devices == null || devices.isEmpty()) { + return; + } + UsbAlsaDevice alsaDevice = devices.peek(); + Slog.d(TAG, "deselect current device:" + alsaDevice); + if (AudioManager.isInputDevice(deviceType)) { + alsaDevice.stopInput(); + } else { + alsaDevice.stopOutput(); + } } /* package */ void usbDeviceAdded(String deviceAddress, UsbDevice usbDevice, @@ -246,6 +300,7 @@ public final class UsbAlsaManager { AlsaCardsParser.AlsaCardRecord cardRec = mCardsParser.findCardNumFor(deviceAddress); if (cardRec == null) { + Slog.e(TAG, "usbDeviceAdded(): cannot find sound card for " + deviceAddress); return; } @@ -275,12 +330,19 @@ public final class UsbAlsaManager { new UsbAlsaDevice(mAudioService, cardRec.getCardNum(), 0 /*device*/, deviceAddress, hasOutput, hasInput, isInputHeadset, isOutputHeadset, isDock); - if (alsaDevice != null) { - alsaDevice.setDeviceNameAndDescription( - cardRec.getCardName(), cardRec.getCardDescription()); - mAlsaDevices.add(0, alsaDevice); - selectAlsaDevice(alsaDevice); + alsaDevice.setDeviceNameAndDescription( + cardRec.getCardName(), cardRec.getCardDescription()); + if (IS_MULTI_MODE) { + deselectCurrentDevice(alsaDevice.getInputDeviceType()); + deselectCurrentDevice(alsaDevice.getOutputDeviceType()); + } else { + // At single mode, the first device is the selected device. + if (!mAlsaDevices.isEmpty()) { + deselectAlsaDevice(mAlsaDevices.get(0)); + } } + addAlsaDevice(alsaDevice); + selectAlsaDevice(alsaDevice); } addMidiDevice(deviceAddress, usbDevice, parser, cardRec); @@ -346,12 +408,20 @@ public final class UsbAlsaManager { } // Audio - UsbAlsaDevice alsaDevice = removeAlsaDeviceFromList(deviceAddress); + UsbAlsaDevice alsaDevice = removeAlsaDevice(deviceAddress); Slog.i(TAG, "USB Audio Device Removed: " + alsaDevice); - if (alsaDevice != null && alsaDevice == mSelectedDevice) { + if (alsaDevice != null) { waitForAlsaDevice(alsaDevice.getCardNum(), false /*isAdded*/); - deselectAlsaDevice(); - selectDefaultDevice(); // if there any external devices left, select one of them + deselectAlsaDevice(alsaDevice); + if (IS_MULTI_MODE) { + selectDefaultDevice(alsaDevice.getOutputDeviceType()); + selectDefaultDevice(alsaDevice.getInputDeviceType()); + } else { + // If there are any external devices left, select the latest attached one + if (!mAlsaDevices.isEmpty() && mAlsaDevices.get(0) != null) { + selectAlsaDevice(mAlsaDevices.get(0)); + } + } } // MIDI @@ -362,7 +432,6 @@ public final class UsbAlsaManager { } logDevices("usbDeviceRemoved()"); - } /* package */ void setPeripheralMidiState(boolean enabled, int card, int device) { diff --git a/services/voiceinteraction/Android.bp b/services/voiceinteraction/Android.bp index 7332d2d8b0f6..de8d1440e6ac 100644 --- a/services/voiceinteraction/Android.bp +++ b/services/voiceinteraction/Android.bp @@ -9,11 +9,60 @@ package { filegroup { name: "services.voiceinteraction-sources", - srcs: ["java/**/*.java"], + srcs: ["java/com/android/server/voiceinteraction/*.java"], path: "java", visibility: ["//frameworks/base/services"], } +filegroup { + name: "services.soundtrigger_middleware-sources", + srcs: ["java/com/android/server/soundtrigger_middleware/*.java"], + path: "java", + visibility: ["//visibility:private"], +} + +filegroup { + name: "services.soundtrigger_service-sources", + srcs: ["java/com/android/server/soundtrigger/*.java"], + path: "java", + visibility: ["//visibility:private"], +} + +filegroup { + name: "services.soundtrigger-sources", + srcs: [ + ":services.soundtrigger_service-sources", + ":services.soundtrigger_middleware-sources", + ], + path: "java", + visibility: ["//frameworks/base/services"], +} + +java_library_static { + name: "services.soundtrigger_middleware", + defaults: ["platform_service_defaults"], + srcs: [":services.soundtrigger_middleware-sources"], + libs: [ + "services.core", + ], + static_libs: [ + "android.hardware.soundtrigger-V2.3-java", + ], + visibility: ["//visibility/base/services/tests/voiceinteraction"], +} + +java_library_static { + name: "services.soundtrigger", + defaults: ["platform_service_defaults"], + srcs: [":services.soundtrigger_service-sources"], + libs: [ + "services.core", + ], + static_libs: [ + "services.soundtrigger_middleware", + ], +} + java_library_static { name: "services.voiceinteraction", defaults: ["platform_service_defaults"], diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING index 5fe1c8d2ecb0..f098155a9bf7 100644 --- a/services/voiceinteraction/TEST_MAPPING +++ b/services/voiceinteraction/TEST_MAPPING @@ -5,6 +5,9 @@ "options": [ { "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceStressTest" } ] }, 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 04c1c0451e63..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; @@ -39,12 +40,13 @@ import android.app.ActivityThread; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.PermissionChecker; import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.hardware.soundtrigger.ConversionUtil; import android.hardware.soundtrigger.IRecognitionStatusCallback; import android.hardware.soundtrigger.ModelParams; -import android.hardware.soundtrigger.ConversionUtil; import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; @@ -64,6 +66,7 @@ import android.media.permission.SafeCloseable; import android.media.soundtrigger.ISoundTriggerDetectionService; import android.media.soundtrigger.ISoundTriggerDetectionServiceClient; import android.media.soundtrigger.SoundTriggerDetectionService; +import android.media.soundtrigger_middleware.ISoundTriggerInjection; import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; import android.os.Binder; import android.os.Bundle; @@ -74,8 +77,8 @@ import android.os.Parcel; import android.os.ParcelUuid; import android.os.PowerManager; import android.os.RemoteException; -import android.os.ServiceSpecificException; import android.os.ServiceManager; +import android.os.ServiceSpecificException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; @@ -86,6 +89,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.app.ISoundTriggerService; import com.android.internal.app.ISoundTriggerSession; +import com.android.server.SoundTriggerInternal; import com.android.server.SystemService; import com.android.server.utils.EventLogger; @@ -98,8 +102,8 @@ import java.util.Map; import java.util.Objects; import java.util.TreeMap; import java.util.UUID; -import java.util.stream.Collectors; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; /** * A single SystemService to manage all sound/voice-based sound models on the DSP. @@ -296,6 +300,23 @@ public class SoundTriggerService extends SystemService { return listUnderlyingModuleProperties(originatorIdentity); } } + + @Override + public void attachInjection(@NonNull ISoundTriggerInjection injection) { + if (PermissionChecker.checkCallingPermissionForPreflight(mContext, + android.Manifest.permission.MANAGE_SOUND_TRIGGER, null) + != PermissionChecker.PERMISSION_GRANTED) { + throw new SecurityException(); + } + try { + ISoundTriggerMiddlewareService.Stub + .asInterface(ServiceManager + .waitForService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE)) + .attachFakeHalInjection(injection); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } class SoundTriggerSessionStub extends ISoundTriggerSession.Stub { @@ -1368,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 @@ -1392,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/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java index aaf7a9eacce6..5846ff699f69 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java @@ -39,7 +39,7 @@ import java.util.UUID; * * @hide */ -public class DatabaseHelper extends SQLiteOpenHelper { +public class DatabaseHelper extends SQLiteOpenHelper implements IEnrolledModelDb { static final String TAG = "SoundModelDBHelper"; static final boolean DBG = false; @@ -153,11 +153,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { } } - /** - * Updates the given keyphrase model, adds it, if it doesn't already exist. - * - * TODO: We only support one keyphrase currently. - */ + @Override public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) { synchronized(this) { SQLiteDatabase db = getWritableDatabase(); @@ -193,9 +189,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { } } - /** - * Deletes the sound model and associated keyphrases. - */ + @Override public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) { // Normalize the locale to guard against SQL injection. bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag(); @@ -218,12 +212,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { } } - /** - * Returns a matching {@link KeyphraseSoundModel} for the keyphrase ID. - * Returns null if a match isn't found. - * - * TODO: We only support one keyphrase currently. - */ + @Override public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) { // Sanitize the locale to guard against SQL injection. @@ -237,12 +226,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { } } - /** - * Returns a matching {@link KeyphraseSoundModel} for the keyphrase string. - * Returns null if a match isn't found. - * - * TODO: We only support one keyphrase currently. - */ + @Override public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle, String bcp47Locale) { // Sanitize the locale to guard against SQL injection. 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/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java new file mode 100644 index 000000000000..f10c2f6a2325 --- /dev/null +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/IEnrolledModelDb.java @@ -0,0 +1,90 @@ +/** + * 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.voiceinteraction; + +import android.hardware.soundtrigger.SoundTrigger.Keyphrase; +import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; + +import java.io.PrintWriter; + +/** + * Interface for registering and querying the enrolled keyphrase model database for + * {@link VoiceInteractionManagerService}. + * This interface only supports one keyphrase per {@link KeyphraseSoundModel}. + * The non-update methods are uniquely keyed on fields of the first keyphrase + * {@link KeyphraseSoundModel#getKeyphrases()}. + * @hide + */ +public interface IEnrolledModelDb { + + //TODO(273286174): We only support one keyphrase currently. + /** + * Register the given {@link KeyphraseSoundModel}, or updates it if it already exists. + * + * @param soundModel - The sound model to register in the database. + * Updates the sound model if the keyphrase id, users, locale match an existing entry. + * Must have one and only one associated {@link Keyphrase}. + * @return - {@code true} if successful, {@code false} if unsuccessful + */ + boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel); + + /** + * Deletes the previously registered keyphrase sound model from the database. + * + * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to delete. + * @param userHandle - The user handle making this request. Must be included in the user + * list of the registered sound model. + * @param bcp47Locale - The locale of the (first) keyphrase associated with this model. + * @return - {@code true} if successful, {@code false} if unsuccessful + */ + boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale); + + //TODO(273286174): We only support one keyphrase currently. + /** + * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair, + * contingent on the userHandle existing in the user list for the model. + * Returns null if a match isn't found. + * + * @param keyphraseId - The (first) keyphrase ID of the KeyphraseSoundModel to query. + * @param userHandle - The user handle making this request. Must be included in the user + * list of the registered sound model. + * @param bcp47Locale - The locale of the (first) keyphrase associated with this model. + * @return - {@code true} if successful, {@code false} if unsuccessful + */ + KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle, + String bcp47Locale); + + //TODO(273286174): We only support one keyphrase currently. + /** + * Returns the first matching {@link KeyphraseSoundModel} for the keyphrase ID, locale pair, + * contingent on the userHandle existing in the user list for the model. + * Returns null if a match isn't found. + * + * @param keyphrase - The text of (the first) keyphrase of the KeyphraseSoundModel to query. + * @param userHandle - The user handle making this request. Must be included in the user + * list of the registered sound model. + * @param bcp47Locale - The locale of the (first) keyphrase associated with this model. + * @return - {@code true} if successful, {@code false} if unsuccessful + */ + KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle, + String bcp47Locale); + + /** + * Dumps contents of database for dumpsys + */ + void dump(PrintWriter pw); +} diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java new file mode 100644 index 000000000000..9bbaf8e25f95 --- /dev/null +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/TestModelEnrollmentDatabase.java @@ -0,0 +1,148 @@ +/** + * 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.voiceinteraction; + +import android.annotation.NonNull; +import android.hardware.soundtrigger.SoundTrigger.Keyphrase; +import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; + +import java.io.PrintWriter; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; + +/** + * In memory model enrollment database for testing purposes. + * @hide + */ +public class TestModelEnrollmentDatabase implements IEnrolledModelDb { + + // Record representing the primary key used in the real model database. + private static final class EnrollmentKey { + private final int mKeyphraseId; + private final List<Integer> mUserIds; + private final String mLocale; + + EnrollmentKey(int keyphraseId, + @NonNull List<Integer> userIds, @NonNull String locale) { + mKeyphraseId = keyphraseId; + mUserIds = Objects.requireNonNull(userIds); + mLocale = Objects.requireNonNull(locale); + } + + int keyphraseId() { + return mKeyphraseId; + } + + List<Integer> userIds() { + return mUserIds; + } + + String locale() { + return mLocale; + } + + @Override + public String toString() { + StringJoiner sj = new StringJoiner(", ", "{", "}"); + sj.add("keyphraseId: " + mKeyphraseId); + sj.add("userIds: " + mUserIds.toString()); + sj.add("locale: " + mLocale.toString()); + return "EnrollmentKey: " + sj.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int res = 1; + res = prime * res + mKeyphraseId; + res = prime * res + mUserIds.hashCode(); + res = prime * res + mLocale.hashCode(); + return res; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (other == null) return false; + if (!(other instanceof EnrollmentKey)) return false; + EnrollmentKey that = (EnrollmentKey) other; + if (mKeyphraseId != that.mKeyphraseId) return false; + if (!mUserIds.equals(that.mUserIds)) return false; + if (!mLocale.equals(that.mLocale)) return false; + return true; + } + + } + + private final Map<EnrollmentKey, KeyphraseSoundModel> mModelMap = new HashMap<>(); + + @Override + public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) { + final Keyphrase keyphrase = soundModel.getKeyphrases()[0]; + mModelMap.put(new EnrollmentKey(keyphrase.getId(), + Arrays.stream(keyphrase.getUsers()).boxed().toList(), + keyphrase.getLocale().toLanguageTag()), + soundModel); + return true; + } + + @Override + public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) { + return mModelMap.keySet().removeIf(key -> (key.keyphraseId() == keyphraseId) + && key.locale().equals(bcp47Locale) + && key.userIds().contains(userHandle)); + } + + @Override + public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle, + String bcp47Locale) { + return mModelMap.entrySet() + .stream() + .filter((entry) -> (entry.getKey().keyphraseId() == keyphraseId) + && entry.getKey().locale().equals(bcp47Locale) + && entry.getKey().userIds().contains(userHandle)) + .findFirst() + .map((entry) -> entry.getValue()) + .orElse(null); + } + + @Override + public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle, + String bcp47Locale) { + return mModelMap.entrySet() + .stream() + .filter((entry) -> (entry.getValue().getKeyphrases()[0].getText().equals(keyphrase) + && entry.getKey().locale().equals(bcp47Locale) + && entry.getKey().userIds().contains(userHandle))) + .findFirst() + .map((entry) -> entry.getValue()) + .orElse(null); + } + + + /** + * Dumps contents of database for dumpsys + */ + public void dump(PrintWriter pw) { + pw.println("Using test enrollment database, with enrolled models:"); + pw.println(mModelMap); + } +} diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index e1da2ca2a086..1d7b966bab51 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -99,11 +99,11 @@ import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.SoundTriggerInternal; import com.android.server.SystemService; import com.android.server.UiThread; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.permission.LegacyPermissionManagerInternal; -import com.android.server.soundtrigger.SoundTriggerInternal; import com.android.server.utils.Slogf; import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.wm.ActivityTaskManagerInternal; @@ -125,7 +125,9 @@ public class VoiceInteractionManagerService extends SystemService { final Context mContext; final ContentResolver mResolver; - final DatabaseHelper mDbHelper; + // Can be overridden for testing purposes + private IEnrolledModelDb mDbHelper; + private final IEnrolledModelDb mRealDbHelper; final ActivityManagerInternal mAmInternal; final ActivityTaskManagerInternal mAtmInternal; final UserManagerInternal mUserManagerInternal; @@ -143,7 +145,7 @@ public class VoiceInteractionManagerService extends SystemService { mResolver = context.getContentResolver(); mUserManagerInternal = Objects.requireNonNull( LocalServices.getService(UserManagerInternal.class)); - mDbHelper = new DatabaseHelper(context); + mDbHelper = mRealDbHelper = new DatabaseHelper(context); mServiceStub = new VoiceInteractionManagerServiceStub(); mAmInternal = Objects.requireNonNull( LocalServices.getService(ActivityManagerInternal.class)); @@ -1605,6 +1607,42 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Override + @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_VOICE_KEYPHRASES) + public void setModelDatabaseForTestEnabled(boolean enabled, IBinder token) { + super.setModelDatabaseForTestEnabled_enforcePermission(); + enforceCallerAllowedToEnrollVoiceModel(); + synchronized (this) { + if (enabled) { + // Replace the dbhelper with a new test db + final var db = new TestModelEnrollmentDatabase(); + try { + // Listen to our caller death, and make sure we revert to the real + // db if they left the model in a test state. + token.linkToDeath(() -> { + synchronized (this) { + if (mDbHelper == db) { + mDbHelper = mRealDbHelper; + mImpl.notifySoundModelsChangedLocked(); + } + } + }, 0); + } catch (RemoteException e) { + // If the caller is already dead, nothing to do. + return; + } + mDbHelper = db; + mImpl.notifySoundModelsChangedLocked(); + } else { + // Nothing to do if the db is already set to the real impl. + if (mDbHelper != mRealDbHelper) { + mDbHelper = mRealDbHelper; + mImpl.notifySoundModelsChangedLocked(); + } + } + } + } + //----------------- SoundTrigger APIs --------------------------------// @Override public boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale) { @@ -1712,28 +1750,27 @@ public class VoiceInteractionManagerService extends SystemService { final long caller = Binder.clearCallingIdentity(); try { KeyphraseSoundModel soundModel = - mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUserId, bcp47Locale); + mDbHelper.getKeyphraseSoundModel(keyphraseId, + callingUserId, bcp47Locale); if (soundModel == null || soundModel.getUuid() == null || soundModel.getKeyphrases() == null) { Slog.w(TAG, "No matching sound model found in startRecognition"); return SoundTriggerInternal.STATUS_ERROR; - } else { - // Regardless of the status of the start recognition, we need to make sure - // that we unload this model if needed later. - synchronized (VoiceInteractionManagerServiceStub.this) { - mLoadedKeyphraseIds.put(keyphraseId, this); - if (mSessionExternalCallback == null - || mSessionInternalCallback == null - || callback.asBinder() != mSessionExternalCallback.asBinder()) { - mSessionInternalCallback = createSoundTriggerCallbackLocked( - callback); - mSessionExternalCallback = callback; - } + } + // Regardless of the status of the start recognition, we need to make sure + // that we unload this model if needed later. + synchronized (VoiceInteractionManagerServiceStub.this) { + mLoadedKeyphraseIds.put(keyphraseId, this); + if (mSessionExternalCallback == null + || mSessionInternalCallback == null + || callback.asBinder() != mSessionExternalCallback.asBinder()) { + mSessionInternalCallback = createSoundTriggerCallbackLocked(callback); + mSessionExternalCallback = callback; } - return mSession.startRecognition(keyphraseId, soundModel, - mSessionInternalCallback, recognitionConfig, runInBatterySaverMode); } + return mSession.startRecognition(keyphraseId, soundModel, + mSessionInternalCallback, recognitionConfig, runInBatterySaverMode); } finally { Binder.restoreCallingIdentity(caller); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 62be2a555bc4..0ad86c11d29a 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -71,7 +71,6 @@ import android.util.PrintWriterPrinter; import android.util.Slog; import android.view.IWindowManager; -import com.android.internal.annotations.GuardedBy; import com.android.internal.app.IHotwordRecognitionStatusCallback; import com.android.internal.app.IVisualQueryDetectionAttentionListener; import com.android.internal.app.IVoiceActionCheckCallback; @@ -248,7 +247,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne Context.RECEIVER_EXPORTED); } - @GuardedBy("this") public void grantImplicitAccessLocked(int grantRecipientUid, @Nullable Intent intent) { final int grantRecipientAppId = UserHandle.getAppId(grantRecipientUid); final int grantRecipientUserId = UserHandle.getUserId(grantRecipientUid); @@ -258,7 +256,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne /* direct= */ true); } - @GuardedBy("this") public boolean showSessionLocked(@Nullable Bundle args, int flags, @Nullable String attributionTag, @Nullable IVoiceInteractionSessionShowCallback showCallback, @@ -331,7 +328,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") public boolean hideSessionLocked() { if (mActiveSession != null) { return mActiveSession.hideLocked(); @@ -339,7 +335,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return false; } - @GuardedBy("this") public boolean deliverNewSessionLocked(IBinder token, IVoiceInteractionSession session, IVoiceInteractor interactor) { if (mActiveSession == null || token != mActiveSession.mToken) { @@ -350,7 +345,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return true; } - @GuardedBy("this") public int startVoiceActivityLocked(@Nullable String callingFeatureId, int callingPid, int callingUid, IBinder token, Intent intent, String resolvedType) { try { @@ -373,7 +367,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") public int startAssistantActivityLocked(@Nullable String callingFeatureId, int callingPid, int callingUid, IBinder token, Intent intent, String resolvedType, @NonNull Bundle bundle) { @@ -397,7 +390,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") public void requestDirectActionsLocked(@NonNull IBinder token, int taskId, @NonNull IBinder assistToken, @Nullable RemoteCallback cancellationCallback, @NonNull RemoteCallback callback) { @@ -453,7 +445,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") void performDirectActionLocked(@NonNull IBinder token, @NonNull String actionId, @Nullable Bundle arguments, int taskId, IBinder assistToken, @Nullable RemoteCallback cancellationCallback, @@ -480,7 +471,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") public void setKeepAwakeLocked(IBinder token, boolean keepAwake) { try { if (mActiveSession == null || token != mActiveSession.mToken) { @@ -493,7 +483,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") public void closeSystemDialogsLocked(IBinder token) { try { if (mActiveSession == null || token != mActiveSession.mToken) { @@ -506,7 +495,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") public void finishLocked(IBinder token, boolean finishTask) { if (mActiveSession == null || (!finishTask && token != mActiveSession.mToken)) { Slog.w(TAG, "finish does not match active session"); @@ -516,7 +504,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mActiveSession = null; } - @GuardedBy("this") public void setDisabledShowContextLocked(int callingUid, int flags) { int activeUid = mInfo.getServiceInfo().applicationInfo.uid; if (callingUid != activeUid) { @@ -526,7 +513,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mDisabledShowContext = flags; } - @GuardedBy("this") public int getDisabledShowContextLocked(int callingUid) { int activeUid = mInfo.getServiceInfo().applicationInfo.uid; if (callingUid != activeUid) { @@ -536,7 +522,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return mDisabledShowContext; } - @GuardedBy("this") public int getUserDisabledShowContextLocked(int callingUid) { int activeUid = mInfo.getServiceInfo().applicationInfo.uid; if (callingUid != activeUid) { @@ -550,7 +535,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return mInfo.getSupportsLocalInteraction(); } - @GuardedBy("this") public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) { if (DEBUG) { Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token); @@ -563,7 +547,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mActiveSession.startListeningVisibleActivityChangedLocked(); } - @GuardedBy("this") public void stopListeningVisibleActivityChangedLocked(@NonNull IBinder token) { if (DEBUG) { Slog.d(TAG, "stopListeningVisibleActivityChangedLocked: token=" + token); @@ -576,7 +559,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mActiveSession.stopListeningVisibleActivityChangedLocked(); } - @GuardedBy("this") public void notifyActivityDestroyedLocked(@NonNull IBinder activityToken) { if (DEBUG) { Slog.d(TAG, "notifyActivityDestroyedLocked activityToken=" + activityToken); @@ -591,7 +573,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mActiveSession.notifyActivityDestroyedLocked(activityToken); } - @GuardedBy("this") public void notifyActivityEventChangedLocked(@NonNull IBinder activityToken, int type) { if (DEBUG) { Slog.d(TAG, "notifyActivityEventChangedLocked type=" + type); @@ -606,7 +587,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mActiveSession.notifyActivityEventChangedLocked(activityToken, type); } - @GuardedBy("this") public void updateStateLocked( @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, @@ -627,7 +607,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") private void verifyDetectorForHotwordDetectionLocked( @Nullable SharedMemory sharedMemory, IHotwordRecognitionStatusCallback callback, @@ -685,7 +664,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne voiceInteractionServiceUid); } - @GuardedBy("this") private void verifyDetectorForVisualQueryDetectionLocked(@Nullable SharedMemory sharedMemory) { Slog.v(TAG, "verifyDetectorForVisualQueryDetectionLocked"); @@ -724,7 +702,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") public void initAndVerifyDetectorLocked( @NonNull Identity voiceInteractorIdentity, @Nullable PersistableBundle options, @@ -769,7 +746,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne detectorType); } - @GuardedBy("this") public void destroyDetectorLocked(IBinder token) { Slog.v(TAG, "destroyDetectorLocked"); @@ -788,7 +764,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") public void shutdownHotwordDetectionServiceLocked() { if (DEBUG) { Slog.d(TAG, "shutdownHotwordDetectionServiceLocked"); @@ -801,7 +776,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mHotwordDetectionConnection = null; } - @GuardedBy("this") public void setVisualQueryDetectionAttentionListenerLocked( @Nullable IVisualQueryDetectionAttentionListener listener) { if (mHotwordDetectionConnection == null) { @@ -810,7 +784,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mHotwordDetectionConnection.setVisualQueryDetectionAttentionListenerLocked(listener); } - @GuardedBy("this") public void startPerceivingLocked(IVisualQueryDetectionVoiceInteractionCallback callback) { if (DEBUG) { Slog.d(TAG, "startPerceivingLocked"); @@ -824,7 +797,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mHotwordDetectionConnection.startPerceivingLocked(callback); } - @GuardedBy("this") public void stopPerceivingLocked() { if (DEBUG) { Slog.d(TAG, "stopPerceivingLocked"); @@ -838,7 +810,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mHotwordDetectionConnection.stopPerceivingLocked(); } - @GuardedBy("this") public void startListeningFromMicLocked( AudioFormat audioFormat, IMicrophoneHotwordDetectionVoiceInteractionCallback callback) { @@ -854,7 +825,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mHotwordDetectionConnection.startListeningFromMicLocked(audioFormat, callback); } - @GuardedBy("this") public void startListeningFromExternalSourceLocked( ParcelFileDescriptor audioStream, AudioFormat audioFormat, @@ -879,7 +849,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne options, token, callback); } - @GuardedBy("this") public void stopListeningFromMicLocked() { if (DEBUG) { Slog.d(TAG, "stopListeningFromMicLocked"); @@ -893,7 +862,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mHotwordDetectionConnection.stopListeningFromMicLocked(); } - @GuardedBy("this") public void triggerHardwareRecognitionEventForTestLocked( SoundTrigger.KeyphraseRecognitionEvent event, IHotwordRecognitionStatusCallback callback) { @@ -908,7 +876,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mHotwordDetectionConnection.triggerHardwareRecognitionEventForTestLocked(event, callback); } - @GuardedBy("this") public IRecognitionStatusCallback createSoundTriggerCallbackLocked( IHotwordRecognitionStatusCallback callback) { if (DEBUG) { @@ -933,12 +900,11 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return null; } - @GuardedBy("this") boolean isIsolatedProcessLocked(@NonNull ServiceInfo serviceInfo) { return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0 && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0; } - @GuardedBy("this") + boolean verifyProcessSharingLocked() { // only check this if both VQDS and HDS are declared in the app ServiceInfo hotwordInfo = getServiceInfoLocked(mHotwordDetectionComponentName, mUser); @@ -960,7 +926,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mHotwordDetectionConnection.forceRestart(); } - @GuardedBy("this") void setDebugHotwordLoggingLocked(boolean logging) { if (mHotwordDetectionConnection == null) { Slog.w(TAG, "Failed to set temporary debug logging: no hotword detection active"); @@ -969,7 +934,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mHotwordDetectionConnection.setDebugHotwordLoggingLocked(logging); } - @GuardedBy("this") void resetHotwordDetectionConnectionLocked() { if (DEBUG) { Slog.d(TAG, "resetHotwordDetectionConnectionLocked"); @@ -984,7 +948,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne mHotwordDetectionConnection = null; } - @GuardedBy("this") public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args) { if (!mValid) { pw.print(" NOT VALID: "); @@ -1023,7 +986,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") void startLocked() { Intent intent = new Intent(VoiceInteractionService.SERVICE_INTERFACE); intent.setComponent(mComponent); @@ -1048,7 +1010,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") void shutdownLocked() { // If there is an active session, cancel it to allow it to clean up its window and other // state. @@ -1076,7 +1037,6 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } } - @GuardedBy("this") void notifySoundModelsChangedLocked() { if (mService == null) { Slog.w(TAG, "Not bound to voice interaction service " + mComponent); diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java index e39af5aa3327..9dd2a61671ec 100644 --- a/telecomm/java/android/telecom/TelecomManager.java +++ b/telecomm/java/android/telecom/TelecomManager.java @@ -2682,71 +2682,76 @@ public class TelecomManager { } /** - * Reports a new call with the specified {@link CallAttributes} to the telecom service. This - * method can be used to report both incoming and outgoing calls. By reporting the call, the - * system is aware of the call and can provide updates on services (ex. Another device wants to - * disconnect the call) or events (ex. a new Bluetooth route became available). - * + * Add a call to the Android system service Telecom. This allows the system to start tracking an + * incoming or outgoing call with the specified {@link CallAttributes}. Once the call is ready + * to be disconnected, use the {@link CallControl#disconnect(DisconnectCause, Executor, + * OutcomeReceiver)} which is provided by the {@code pendingControl#onResult(CallControl)}. * <p> - * The difference between this API call and {@link TelecomManager#placeCall(Uri, Bundle)} or - * {@link TelecomManager#addNewIncomingCall(PhoneAccountHandle, Bundle)} is that this API - * will asynchronously provide an update on whether the new call was added successfully via - * an {@link OutcomeReceiver}. Additionally, callbacks will run on the executor thread that was - * passed in. - * * <p> - * Note: Only packages that register with + * <p> + * <b>Call Lifecycle</b>: Your app is given foreground execution priority as long as you have a + * valid call and are posting a {@link android.app.Notification.CallStyle} notification. + * When your application is given foreground execution priority, your app is treated as a + * foreground service. Foreground execution priority will prevent the + * {@link android.app.ActivityManager} from killing your application when it is placed the + * background. Foreground execution priority is removed from your app when all of your app's + * calls terminate or your app no longer posts a valid notification. + * <p> + * <p> + * <p> + * <b>Note</b>: Only packages that register with * {@link PhoneAccount#CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS} * can utilize this API. {@link PhoneAccount}s that set the capabilities * {@link PhoneAccount#CAPABILITY_SIM_SUBSCRIPTION}, * {@link PhoneAccount#CAPABILITY_CALL_PROVIDER}, * {@link PhoneAccount#CAPABILITY_CONNECTION_MANAGER} * are not supported and will cause an exception to be thrown. - * * <p> - * Usage example: + * <p> + * <p> + * <b>Usage example:</b> * <pre> - * - * // An app should first define their own construct of a Call that overrides all the - * // {@link CallControlCallback}s and {@link CallEventCallback}s - * private class MyVoipCall { - * public String callId = ""; - * - * public CallControlCallEventCallback handshakes = new - * CallControlCallEventCallback() { - * // override/ implement all {@link CallControlCallback}s + * // Its up to your app on how you want to wrap the objects. One such implementation can be: + * class MyVoipCall { + * ... + * public CallControlCallEventCallback handshakes = new CallControlCallback() { + * ... * } - * public CallEventCallback events = new - * CallEventCallback() { - * // override/ implement all {@link CallEventCallback}s - * } - * public MyVoipCall(String id){ - * callId = id; - * } * - * PhoneAccountHandle handle = new PhoneAccountHandle( - * new ComponentName("com.example.voip.app", - * "com.example.voip.app.NewCallActivity"), "123"); + * public CallEventCallback events = new CallEventCallback() { + * ... + * } * - * CallAttributes callAttributes = new CallAttributes.Builder(handle, - * CallAttributes.DIRECTION_OUTGOING, - * "John Smith", Uri.fromParts("tel", "123", null)) - * .build(); + * public MyVoipCall(String id){ + * ... + * } + * } * * MyVoipCall myFirstOutgoingCall = new MyVoipCall("1"); * - * telecomManager.addCall(callAttributes, Runnable::run, new OutcomeReceiver() { + * telecomManager.addCall(callAttributes, + * Runnable::run, + * new OutcomeReceiver() { * public void onResult(CallControl callControl) { - * // The call has been added successfully + * // The call has been added successfully. For demonstration + * // purposes, the call is disconnected immediately ... + * callControl.disconnect( + * new DisconnectCause(DisconnectCause.LOCAL) ) * } - * }, myFirstOutgoingCall.handshakes, myFirstOutgoingCall.events); + * }, + * myFirstOutgoingCall.handshakes, + * myFirstOutgoingCall.events); * </pre> * - * @param callAttributes attributes of the new call (incoming or outgoing, address, etc. ) - * @param executor thread to run background CallEventCallback updates on - * @param pendingControl OutcomeReceiver that receives the result of addCall transaction - * @param handshakes object that overrides {@link CallControlCallback}s - * @param events object that overrides {@link CallEventCallback}s + * @param callAttributes attributes of the new call (incoming or outgoing, address, etc.) + * @param executor execution context to run {@link CallControlCallback} updates on + * @param pendingControl Receives the result of addCall transaction. Upon success, a + * CallControl object is provided which can be used to do things like + * disconnect the call that was added. + * @param handshakes callback that receives <b>actionable</b> updates that originate from + * Telecom. + * @param events callback that receives <b>non</b>-actionable updates that originate + * from Telecom. */ @RequiresPermission(android.Manifest.permission.MANAGE_OWN_CALLS) @SuppressLint("SamShouldBeLast") 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/AntennaDirection.aidl b/telephony/java/android/telephony/satellite/AntennaDirection.aidl new file mode 100644 index 000000000000..c838f6fbb8ac --- /dev/null +++ b/telephony/java/android/telephony/satellite/AntennaDirection.aidl @@ -0,0 +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 android.telephony.satellite; + +parcelable AntennaDirection; diff --git a/telephony/java/android/telephony/satellite/AntennaDirection.java b/telephony/java/android/telephony/satellite/AntennaDirection.java new file mode 100644 index 000000000000..02b0bc7364a2 --- /dev/null +++ b/telephony/java/android/telephony/satellite/AntennaDirection.java @@ -0,0 +1,138 @@ +/* + * 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.telephony.satellite; + +import android.annotation.NonNull; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Antenna direction is provided as X/Y/Z values corresponding to the direction of the antenna + * main lobe as a unit vector in CTIA coordinate system (as specified in Appendix A of Wireless + * device CTIA OTAn test plan). CTIA coordinate system is defined relative to device’s screen + * when the device is held in default portrait mode with screen facing the user: + * + * Z axis is vertical along the plane of the device with positive Z pointing up and negative z + * pointing towards bottom of the device + * Y axis is horizontal along the plane of the device with positive Y pointing towards right of + * the phone screen and negative Y pointing towards left + * X axis is orthogonal to the Y-Z plane (phone screen), pointing away from the phone screen for + * positive X and pointing away from back of the phone for negative X. + * @hide + */ +public final class AntennaDirection implements Parcelable { + /** Antenna x axis direction. */ + private float mX; + + /** Antenna y axis direction. */ + private float mY; + + /** Antenna z axis direction. */ + private float mZ; + + /** + * @hide + */ + @UnsupportedAppUsage + public AntennaDirection(float x, float y, float z) { + mX = x; + mY = y; + mZ = z; + } + + private AntennaDirection(Parcel in) { + readFromParcel(in); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeFloat(mX); + out.writeFloat(mY); + out.writeFloat(mZ); + } + + @NonNull + public static final Creator<AntennaDirection> CREATOR = + new Creator<>() { + @Override + public AntennaDirection createFromParcel(Parcel in) { + return new AntennaDirection(in); + } + + @Override + public AntennaDirection[] newArray(int size) { + return new AntennaDirection[size]; + } + }; + + @Override + @NonNull public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("X:"); + sb.append(mX); + sb.append(","); + + sb.append("Y:"); + sb.append(mY); + sb.append(","); + + sb.append("Z:"); + sb.append(mZ); + return sb.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AntennaDirection that = (AntennaDirection) o; + return mX == that.mX + && mY == that.mY + && mZ == that.mZ; + } + + @Override + public int hashCode() { + return Objects.hash(mX, mY, mZ); + } + + public float getX() { + return mX; + } + + public float getY() { + return mY; + } + + public float getZ() { + return mZ; + } + + private void readFromParcel(Parcel in) { + mX = in.readFloat(); + mY = in.readFloat(); + mZ = in.readFloat(); + } +} diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.aidl b/telephony/java/android/telephony/satellite/AntennaPosition.aidl new file mode 100644 index 000000000000..00525624329c --- /dev/null +++ b/telephony/java/android/telephony/satellite/AntennaPosition.aidl @@ -0,0 +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 android.telephony.satellite; + +parcelable AntennaPosition; diff --git a/telephony/java/android/telephony/satellite/AntennaPosition.java b/telephony/java/android/telephony/satellite/AntennaPosition.java new file mode 100644 index 000000000000..eefc8b00f8e8 --- /dev/null +++ b/telephony/java/android/telephony/satellite/AntennaPosition.java @@ -0,0 +1,117 @@ +/* + * 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.telephony.satellite; + +import android.annotation.NonNull; +import android.compat.annotation.UnsupportedAppUsage; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Antenna Position received from satellite modem which gives information about antenna + * direction to be used with satellite communication and suggested device hold positions. + * @hide + */ +public final class AntennaPosition implements Parcelable { + /** Antenna direction used for satellite communication. */ + @NonNull AntennaDirection mAntennaDirection; + + /** Enum corresponding to device hold position to be used by the end user. */ + @SatelliteManager.DeviceHoldPosition int mSuggestedHoldPosition; + + /** + * @hide + */ + @UnsupportedAppUsage + public AntennaPosition(@NonNull AntennaDirection antennaDirection, int suggestedHoldPosition) { + mAntennaDirection = antennaDirection; + mSuggestedHoldPosition = suggestedHoldPosition; + } + + private AntennaPosition(Parcel in) { + readFromParcel(in); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeParcelable(mAntennaDirection, flags); + out.writeInt(mSuggestedHoldPosition); + } + + @NonNull + public static final Creator<AntennaPosition> CREATOR = + new Creator<>() { + @Override + public AntennaPosition createFromParcel(Parcel in) { + return new AntennaPosition(in); + } + + @Override + public AntennaPosition[] newArray(int size) { + return new AntennaPosition[size]; + } + }; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AntennaPosition that = (AntennaPosition) o; + return Objects.equals(mAntennaDirection, that.mAntennaDirection) + && mSuggestedHoldPosition == that.mSuggestedHoldPosition; + } + + @Override + public int hashCode() { + return Objects.hash(mAntennaDirection, mSuggestedHoldPosition); + } + + @Override + @NonNull public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("antennaDirection:"); + sb.append(mAntennaDirection); + sb.append(","); + + sb.append("suggestedHoldPosition:"); + sb.append(mSuggestedHoldPosition); + return sb.toString(); + } + + @NonNull + public AntennaDirection getAntennaDirection() { + return mAntennaDirection; + } + + @SatelliteManager.DeviceHoldPosition + public int getSuggestedHoldPosition() { + return mSuggestedHoldPosition; + } + + private void readFromParcel(Parcel in) { + mAntennaDirection = in.readParcelable(AntennaDirection.class.getClassLoader(), + AntennaDirection.class); + mSuggestedHoldPosition = in.readInt(); + } +} diff --git a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java index 87c8db317195..00928904a4c4 100644 --- a/telephony/java/android/telephony/satellite/SatelliteCapabilities.java +++ b/telephony/java/android/telephony/satellite/SatelliteCapabilities.java @@ -21,7 +21,10 @@ import android.compat.annotation.UnsupportedAppUsage; import android.os.Parcel; import android.os.Parcelable; +import java.util.HashMap; import java.util.HashSet; +import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -44,15 +47,25 @@ public final class SatelliteCapabilities implements Parcelable { private int mMaxBytesPerOutgoingDatagram; /** + * Antenna Position received from satellite modem which gives information about antenna + * direction to be used with satellite communication and suggested device hold positions. + * Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition + */ + @NonNull + private Map<Integer, AntennaPosition> mAntennaPositionMap; + + /** * @hide */ @UnsupportedAppUsage public SatelliteCapabilities(Set<Integer> supportedRadioTechnologies, - boolean isPointingRequired, int maxBytesPerOutgoingDatagram) { + boolean isPointingRequired, int maxBytesPerOutgoingDatagram, + @NonNull Map<Integer, AntennaPosition> antennaPositionMap) { mSupportedRadioTechnologies = supportedRadioTechnologies == null ? new HashSet<>() : supportedRadioTechnologies; mIsPointingRequired = isPointingRequired; mMaxBytesPerOutgoingDatagram = maxBytesPerOutgoingDatagram; + mAntennaPositionMap = antennaPositionMap; } private SatelliteCapabilities(Parcel in) { @@ -77,6 +90,17 @@ public final class SatelliteCapabilities implements Parcelable { out.writeBoolean(mIsPointingRequired); out.writeInt(mMaxBytesPerOutgoingDatagram); + + if (mAntennaPositionMap != null && !mAntennaPositionMap.isEmpty()) { + int size = mAntennaPositionMap.size(); + out.writeInt(size); + for (Map.Entry<Integer, AntennaPosition> entry : mAntennaPositionMap.entrySet()) { + out.writeInt(entry.getKey()); + out.writeParcelable(entry.getValue(), flags); + } + } else { + out.writeInt(0); + } } @NonNull public static final Creator<SatelliteCapabilities> CREATOR = new Creator<>() { @@ -109,11 +133,32 @@ public final class SatelliteCapabilities implements Parcelable { sb.append(mIsPointingRequired); sb.append(","); - sb.append("maxBytesPerOutgoingDatagram"); + sb.append("maxBytesPerOutgoingDatagram:"); sb.append(mMaxBytesPerOutgoingDatagram); + sb.append(","); + + sb.append("antennaPositionMap:"); + sb.append(mAntennaPositionMap); return sb.toString(); } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SatelliteCapabilities that = (SatelliteCapabilities) o; + return Objects.equals(mSupportedRadioTechnologies, that.mSupportedRadioTechnologies) + && mIsPointingRequired == that.mIsPointingRequired + && mMaxBytesPerOutgoingDatagram == that.mMaxBytesPerOutgoingDatagram + && Objects.equals(mAntennaPositionMap, that.mAntennaPositionMap); + } + + @Override + public int hashCode() { + return Objects.hash(mSupportedRadioTechnologies, mIsPointingRequired, + mMaxBytesPerOutgoingDatagram, mAntennaPositionMap); + } + /** * @return The list of technologies supported by the satellite modem. */ @@ -141,6 +186,16 @@ public final class SatelliteCapabilities implements Parcelable { return mMaxBytesPerOutgoingDatagram; } + /** + * Antenna Position received from satellite modem which gives information about antenna + * direction to be used with satellite communication and suggested device hold positions. + * @return Map key: {@link SatelliteManager.DeviceHoldPosition} value: AntennaPosition + */ + @NonNull + public Map<Integer, AntennaPosition> getAntennaPositionMap() { + return mAntennaPositionMap; + } + private void readFromParcel(Parcel in) { mSupportedRadioTechnologies = new HashSet<>(); int numSupportedRadioTechnologies = in.readInt(); @@ -152,5 +207,14 @@ public final class SatelliteCapabilities implements Parcelable { mIsPointingRequired = in.readBoolean(); mMaxBytesPerOutgoingDatagram = in.readInt(); + + mAntennaPositionMap = new HashMap<>(); + int antennaPositionMapSize = in.readInt(); + for (int i = 0; i < antennaPositionMapSize; i++) { + int key = in.readInt(); + AntennaPosition antennaPosition = in.readParcelable( + AntennaPosition.class.getClassLoader(), AntennaPosition.class); + mAntennaPositionMap.put(key, antennaPosition); + } } } diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 20f9bc8bef05..5681ab266c17 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -334,6 +334,46 @@ public class SatelliteManager { @Retention(RetentionPolicy.SOURCE) public @interface NTRadioTechnology {} + /** Suggested device hold position is unknown. */ + public static final int DEVICE_HOLD_POSITION_UNKNOWN = 0; + /** User is suggested to hold the device in portrait mode. */ + public static final int DEVICE_HOLD_POSITION_PORTRAIT = 1; + /** User is suggested to hold the device in landscape mode with left hand. */ + public static final int DEVICE_HOLD_POSITION_LANDSCAPE_LEFT = 2; + /** User is suggested to hold the device in landscape mode with right hand. */ + public static final int DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT = 3; + + /** @hide */ + @IntDef(prefix = {"DEVICE_HOLD_POSITION_"}, value = { + DEVICE_HOLD_POSITION_UNKNOWN, + DEVICE_HOLD_POSITION_PORTRAIT, + DEVICE_HOLD_POSITION_LANDSCAPE_LEFT, + DEVICE_HOLD_POSITION_LANDSCAPE_RIGHT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceHoldPosition {} + + /** Display mode is unknown. */ + public static final int DISPLAY_MODE_UNKNOWN = 0; + /** Display mode of the device used for satellite communication for non-foldable phones. */ + public static final int DISPLAY_MODE_FIXED = 1; + /** Display mode of the device used for satellite communication for foldabale phones when the + * device is opened. */ + public static final int DISPLAY_MODE_OPENED = 2; + /** Display mode of the device used for satellite communication for foldabable phones when the + * device is closed. */ + public static final int DISPLAY_MODE_CLOSED = 3; + + /** @hide */ + @IntDef(prefix = {"ANTENNA_POSITION_"}, value = { + DISPLAY_MODE_UNKNOWN, + DISPLAY_MODE_FIXED, + DISPLAY_MODE_OPENED, + DISPLAY_MODE_CLOSED + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DisplayMode {} + /** * Request to enable or disable the satellite modem and demo mode. If the satellite modem is * enabled, this may also disable the cellular modem, and if the satellite modem is disabled, diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl index cd69da18c5b0..eaf96abeb80a 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl +++ b/telephony/java/android/telephony/satellite/stub/SatelliteCapabilities.aidl @@ -17,7 +17,7 @@ package android.telephony.satellite.stub; import android.telephony.satellite.stub.NTRadioTechnology; - +import android.telephony.satellite.AntennaPosition; /** * {@hide} */ @@ -36,4 +36,14 @@ parcelable SatelliteCapabilities { * The maximum number of bytes per datagram that can be sent over satellite. */ int maxBytesPerOutgoingDatagram; + + /** + * Keys which are used to fill mAntennaPositionMap. + */ + int[] antennaPositionKeys; + + /** + * Antenna Position for different display modes received from satellite modem. + */ + AntennaPosition[] antennaPositionValues; } diff --git a/tests/FlickerTests/AndroidTest.xml b/tests/FlickerTests/AndroidTest.xml index f2ffc19f2a4e..7272abba897d 100644 --- a/tests/FlickerTests/AndroidTest.xml +++ b/tests/FlickerTests/AndroidTest.xml @@ -29,6 +29,7 @@ <option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" /> <option name="teardown-command" value="settings delete system show_touches" /> <option name="teardown-command" value="settings delete system pointer_location" /> + <option name="teardown-command" value="cmd overlay enable com.android.internal.systemui.navbar.gestural" /> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> <option name="cleanup-apks" value="true"/> diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java index f8d885ae3faf..d7fa124623ce 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -417,9 +417,9 @@ public class PackageWatchdogTest { int failureReason, int mitigationCount) { if (versionedPackage.getVersionCode() == VERSION_CODE) { // Only rollback for specific versionCode - return PackageHealthObserverImpact.USER_IMPACT_MEDIUM; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; } - return PackageHealthObserverImpact.USER_IMPACT_NONE; + return PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; } }; @@ -442,13 +442,13 @@ public class PackageWatchdogTest { public void testPackageFailureNotifyAllDifferentImpacts() throws Exception { PackageWatchdog watchdog = createWatchdog(); TestObserver observerNone = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_NONE); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_0); TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); TestObserver observerMid = new TestObserver(OBSERVER_NAME_3, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); TestObserver observerLow = new TestObserver(OBSERVER_NAME_4, - PackageHealthObserverImpact.USER_IMPACT_LOW); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); // Start observing for all impact observers watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), @@ -499,9 +499,9 @@ public class PackageWatchdogTest { public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception { PackageWatchdog watchdog = createWatchdog(); TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_LOW); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing for observerFirst and observerSecond with failure handling watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION); @@ -517,7 +517,7 @@ public class PackageWatchdogTest { assertThat(observerSecond.mMitigatedPackages).isEmpty(); // After observerFirst handles failure, next action it has is high impact - observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH; + observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_100; observerFirst.mMitigatedPackages.clear(); observerSecond.mMitigatedPackages.clear(); @@ -531,7 +531,7 @@ public class PackageWatchdogTest { assertThat(observerFirst.mMitigatedPackages).isEmpty(); // After observerSecond handles failure, it has no further actions - observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; + observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; observerFirst.mMitigatedPackages.clear(); observerSecond.mMitigatedPackages.clear(); @@ -545,7 +545,7 @@ public class PackageWatchdogTest { assertThat(observerSecond.mMitigatedPackages).isEmpty(); // After observerFirst handles failure, it too has no further actions - observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; + observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_0; observerFirst.mMitigatedPackages.clear(); observerSecond.mMitigatedPackages.clear(); @@ -566,9 +566,9 @@ public class PackageWatchdogTest { public void testPackageFailureNotifyOneSameImpact() throws Exception { PackageWatchdog watchdog = createWatchdog(); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); // Start observing for observer1 and observer2 with failure handling watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); @@ -592,11 +592,11 @@ public class PackageWatchdogTest { TestController controller = new TestController(); PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); TestObserver observer3 = new TestObserver(OBSERVER_NAME_3, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); // Start observing with explicit health checks for APP_A and APP_B respectively @@ -645,7 +645,7 @@ public class PackageWatchdogTest { TestController controller = new TestController(); PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing with explicit health checks for APP_A and APP_B controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C)); @@ -711,7 +711,7 @@ public class PackageWatchdogTest { TestController controller = new TestController(); PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing with explicit health checks for APP_A and // package observation duration == LONG_DURATION @@ -742,7 +742,7 @@ public class PackageWatchdogTest { TestController controller = new TestController(); PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); // Start observing with explicit health checks for APP_A and // package observation duration == SHORT_DURATION / 2 @@ -818,7 +818,7 @@ public class PackageWatchdogTest { // Start observing with failure handling TestObserver observer = new TestObserver(OBSERVER_NAME_1, - PackageHealthObserverImpact.USER_IMPACT_HIGH); + PackageHealthObserverImpact.USER_IMPACT_LEVEL_100); wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION); // Notify of NetworkStack failure @@ -1073,9 +1073,9 @@ public class PackageWatchdogTest { public void testBootLoopMitigationDoneForLowestUserImpact() { PackageWatchdog watchdog = createWatchdog(); TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1); - bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW); + bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_10); TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2); - bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM); + bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_LEVEL_30); watchdog.registerHealthObserver(bootObserver1); watchdog.registerHealthObserver(bootObserver2); for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) { @@ -1446,7 +1446,7 @@ public class PackageWatchdogTest { TestObserver(String name) { mName = name; - mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM; + mImpact = PackageHealthObserverImpact.USER_IMPACT_LEVEL_30; } TestObserver(String name, int impact) { 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 2bfb04fdb765..1731f6be4bf2 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"> + <application android:debuggable="true" android:testOnly="true"> <uses-library android:name="android.test.runner" /> </application> diff --git a/tests/testables/tests/AndroidTest.xml b/tests/testables/tests/AndroidTest.xml new file mode 100644 index 000000000000..6d2979423efa --- /dev/null +++ b/tests/testables/tests/AndroidTest.xml @@ -0,0 +1,27 @@ +<?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> diff --git a/tools/aapt/SdkConstants.h b/tools/aapt/SdkConstants.h index a146466402f6..e2c161482857 100644 --- a/tools/aapt/SdkConstants.h +++ b/tools/aapt/SdkConstants.h @@ -49,6 +49,7 @@ enum { SDK_S = 31, SDK_S_V2 = 32, SDK_TIRAMISU = 33, + SDK_UPSIDE_DOWN_CAKE = 34, SDK_CUR_DEVELOPMENT = 10000, }; diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index 40bcef787815..e47704ea2725 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -59,6 +59,7 @@ enum : ApiVersion { SDK_S = 31, SDK_S_V2 = 32, SDK_TIRAMISU = 33, + SDK_UPSIDE_DOWN_CAKE = 34, SDK_CUR_DEVELOPMENT = 10000, }; diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java index 25fbabce71ae..166fbddc2f56 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java @@ -163,11 +163,11 @@ public final class NetworkProviderInfo implements Parcelable { /** * Sets the displayed connection strength of the remote device to the internet. * - * @param connectionStrength Connection strength in range 0 to 3. + * @param connectionStrength Connection strength in range 0 to 4. * @return Returns the Builder object. */ @NonNull - public Builder setConnectionStrength(@IntRange(from = 0, to = 3) int connectionStrength) { + public Builder setConnectionStrength(@IntRange(from = 0, to = 4) int connectionStrength) { mConnectionStrength = connectionStrength; return this; } @@ -205,8 +205,8 @@ public final class NetworkProviderInfo implements Parcelable { if (batteryPercentage < 0 || batteryPercentage > 100) { throw new IllegalArgumentException("BatteryPercentage must be in range 0-100"); } - if (connectionStrength < 0 || connectionStrength > 3) { - throw new IllegalArgumentException("ConnectionStrength must be in range 0-3"); + if (connectionStrength < 0 || connectionStrength > 4) { + throw new IllegalArgumentException("ConnectionStrength must be in range 0-4"); } } @@ -265,9 +265,9 @@ public final class NetworkProviderInfo implements Parcelable { /** * Gets the displayed connection strength of the remote device to the internet. * - * @return Returns the connection strength in range 0 to 3. + * @return Returns the connection strength in range 0 to 4. */ - @IntRange(from = 0, to = 3) + @IntRange(from = 0, to = 4) public int getConnectionStrength() { return mConnectionStrength; } |