diff options
159 files changed, 3508 insertions, 2274 deletions
diff --git a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java index d3938f4c0926..afd8e2948c41 100644 --- a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java +++ b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java @@ -77,7 +77,7 @@ public class ResourcesManagerPerfTest { } private void getResourcesForPath(String path) { - ResourcesManager.getInstance().getResources(null, path, null, null, null, + ResourcesManager.getInstance().getResources(null, path, null, null, null, null, Display.DEFAULT_DISPLAY, null, sContext.getResources().getCompatibilityInfo(), null, null); } diff --git a/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java index f4c0a172710b..45c723bea9db 100644 --- a/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java +++ b/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java @@ -95,8 +95,9 @@ public class ResourcesThemePerfTest { ? Configuration.ORIENTATION_LANDSCAPE : Configuration.ORIENTATION_PORTRAIT; Resources destResources = resourcesManager.getResources(null, ai.sourceDir, - ai.splitSourceDirs, ai.resourceDirs, ai.sharedLibraryFiles, Display.DEFAULT_DISPLAY, - c, mContext.getResources().getCompatibilityInfo(), null, null); + ai.splitSourceDirs, ai.resourceDirs, ai.overlayPaths, ai.sharedLibraryFiles, + Display.DEFAULT_DISPLAY, c, mContext.getResources().getCompatibilityInfo(), + null, null); Assert.assertNotEquals(destResources.getAssets(), mContext.getAssets()); Resources.Theme destTheme = destResources.newTheme(); diff --git a/apex/appsearch/framework/api/current.txt b/apex/appsearch/framework/api/current.txt index a3b80139ced8..905000ac7c3d 100644 --- a/apex/appsearch/framework/api/current.txt +++ b/apex/appsearch/framework/api/current.txt @@ -208,8 +208,8 @@ package android.app.appsearch { ctor public GetByUriRequest.Builder(); method @NonNull public android.app.appsearch.GetByUriRequest.Builder addProjection(@NonNull String, @NonNull java.lang.String...); method @NonNull public android.app.appsearch.GetByUriRequest.Builder addProjection(@NonNull String, @NonNull java.util.Collection<java.lang.String>); - method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUri(@NonNull java.lang.String...); - method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUri(@NonNull java.util.Collection<java.lang.String>); + method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUris(@NonNull java.lang.String...); + method @NonNull public android.app.appsearch.GetByUriRequest.Builder addUris(@NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.GetByUriRequest build(); method @NonNull public android.app.appsearch.GetByUriRequest.Builder setNamespace(@NonNull String); } @@ -243,8 +243,8 @@ package android.app.appsearch { public static final class RemoveByUriRequest.Builder { ctor public RemoveByUriRequest.Builder(); - method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUri(@NonNull java.lang.String...); - method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUri(@NonNull java.util.Collection<java.lang.String>); + method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUris(@NonNull java.lang.String...); + method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder addUris(@NonNull java.util.Collection<java.lang.String>); method @NonNull public android.app.appsearch.RemoveByUriRequest build(); method @NonNull public android.app.appsearch.RemoveByUriRequest.Builder setNamespace(@NonNull String); } diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java index 0fcf0613dd38..656608d82ad4 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/GetByUriRequest.java @@ -122,14 +122,14 @@ public final class GetByUriRequest { /** Adds one or more URIs to the request. */ @NonNull - public Builder addUri(@NonNull String... uris) { + public Builder addUris(@NonNull String... uris) { Preconditions.checkNotNull(uris); - return addUri(Arrays.asList(uris)); + return addUris(Arrays.asList(uris)); } /** Adds one or more URIs to the request. */ @NonNull - public Builder addUri(@NonNull Collection<String> uris) { + public Builder addUris(@NonNull Collection<String> uris) { Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkNotNull(uris); mUris.addAll(uris); diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java index 2104198d371c..198eee85be53 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/RemoveByUriRequest.java @@ -73,14 +73,14 @@ public final class RemoveByUriRequest { /** Adds one or more URIs to the request. */ @NonNull - public Builder addUri(@NonNull String... uris) { + public Builder addUris(@NonNull String... uris) { Preconditions.checkNotNull(uris); - return addUri(Arrays.asList(uris)); + return addUris(Arrays.asList(uris)); } /** Adds one or more URIs to the request. */ @NonNull - public Builder addUri(@NonNull Collection<String> uris) { + public Builder addUris(@NonNull Collection<String> uris) { Preconditions.checkState(!mBuilt, "Builder has already been used"); Preconditions.checkNotNull(uris); mUris.addAll(uris); diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java index 0328d5453ab8..4869aa38b5fd 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/SetSchemaResponse.java @@ -163,7 +163,7 @@ public class SetSchemaResponse { /** Adds deletedTypes to the list of deleted schema types. */ @NonNull - public Builder addDeletedType(@NonNull Collection<String> deletedTypes) { + public Builder addDeletedTypes(@NonNull Collection<String> deletedTypes) { Preconditions.checkState(!mBuilt, "Builder has already been used"); mDeletedTypes.addAll(Preconditions.checkNotNull(deletedTypes)); return this; @@ -171,7 +171,7 @@ public class SetSchemaResponse { /** Adds incompatibleTypes to the list of incompatible schema types. */ @NonNull - public Builder addIncompatibleType(@NonNull Collection<String> incompatibleTypes) { + public Builder addIncompatibleTypes(@NonNull Collection<String> incompatibleTypes) { Preconditions.checkState(!mBuilt, "Builder has already been used"); mIncompatibleTypes.addAll(Preconditions.checkNotNull(incompatibleTypes)); return this; @@ -179,7 +179,7 @@ public class SetSchemaResponse { /** Adds migratedTypes to the list of migrated schema types. */ @NonNull - public Builder addMigratedType(@NonNull Collection<String> migratedTypes) { + public Builder addMigratedTypes(@NonNull Collection<String> migratedTypes) { Preconditions.checkState(!mBuilt, "Builder has already been used"); mMigratedTypes.addAll(Preconditions.checkNotNull(migratedTypes)); return this; diff --git a/apex/appsearch/framework/java/external/android/app/appsearch/util/BundleUtil.java b/apex/appsearch/framework/java/external/android/app/appsearch/util/BundleUtil.java index 1b4d28401ea0..14dd472c9b9c 100644 --- a/apex/appsearch/framework/java/external/android/app/appsearch/util/BundleUtil.java +++ b/apex/appsearch/framework/java/external/android/app/appsearch/util/BundleUtil.java @@ -147,35 +147,41 @@ public final class BundleUtil { if (bundle == null) { return 0; } - int[] hashCodes = new int[bundle.size()]; - int i = 0; + int[] hashCodes = new int[bundle.size() + 1]; + int hashCodeIdx = 0; // Bundle inherit its hashCode() from Object.java, which only relative to their memory // address. Bundle doesn't have an order, so we should iterate all keys and combine // their value's hashcode into an array. And use the hashcode of the array to be // the hashcode of the bundle. - for (String key : bundle.keySet()) { - Object value = bundle.get(key); + // Because bundle.keySet() doesn't guarantee any particular order, we need to sort the keys + // in case the iteration order varies from run to run. + String[] keys = bundle.keySet().toArray(new String[0]); + Arrays.sort(keys); + // Hash the keys so we can detect key-only differences + hashCodes[hashCodeIdx++] = Arrays.hashCode(keys); + for (int keyIdx = 0; keyIdx < keys.length; keyIdx++) { + Object value = bundle.get(keys[keyIdx]); if (value instanceof Bundle) { - hashCodes[i++] = deepHashCode((Bundle) value); + hashCodes[hashCodeIdx++] = deepHashCode((Bundle) value); } else if (value instanceof int[]) { - hashCodes[i++] = Arrays.hashCode((int[]) value); + hashCodes[hashCodeIdx++] = Arrays.hashCode((int[]) value); } else if (value instanceof byte[]) { - hashCodes[i++] = Arrays.hashCode((byte[]) value); + hashCodes[hashCodeIdx++] = Arrays.hashCode((byte[]) value); } else if (value instanceof char[]) { - hashCodes[i++] = Arrays.hashCode((char[]) value); + hashCodes[hashCodeIdx++] = Arrays.hashCode((char[]) value); } else if (value instanceof long[]) { - hashCodes[i++] = Arrays.hashCode((long[]) value); + hashCodes[hashCodeIdx++] = Arrays.hashCode((long[]) value); } else if (value instanceof float[]) { - hashCodes[i++] = Arrays.hashCode((float[]) value); + hashCodes[hashCodeIdx++] = Arrays.hashCode((float[]) value); } else if (value instanceof short[]) { - hashCodes[i++] = Arrays.hashCode((short[]) value); + hashCodes[hashCodeIdx++] = Arrays.hashCode((short[]) value); } else if (value instanceof double[]) { - hashCodes[i++] = Arrays.hashCode((double[]) value); + hashCodes[hashCodeIdx++] = Arrays.hashCode((double[]) value); } else if (value instanceof boolean[]) { - hashCodes[i++] = Arrays.hashCode((boolean[]) value); + hashCodes[hashCodeIdx++] = Arrays.hashCode((boolean[]) value); } else if (value instanceof String[]) { // Optimization to avoid Object[] handler creating an inner array for common cases - hashCodes[i++] = Arrays.hashCode((String[]) value); + hashCodes[hashCodeIdx++] = Arrays.hashCode((String[]) value); } else if (value instanceof Object[]) { Object[] array = (Object[]) value; int[] innerHashCodes = new int[array.length]; @@ -186,7 +192,7 @@ public final class BundleUtil { innerHashCodes[j] = array[j].hashCode(); } } - hashCodes[i++] = Arrays.hashCode(innerHashCodes); + hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes); } else if (value instanceof ArrayList) { ArrayList<?> list = (ArrayList<?>) value; int[] innerHashCodes = new int[list.size()]; @@ -198,7 +204,7 @@ public final class BundleUtil { innerHashCodes[j] = item.hashCode(); } } - hashCodes[i++] = Arrays.hashCode(innerHashCodes); + hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes); } else if (value instanceof SparseArray) { SparseArray<?> array = (SparseArray<?>) value; int[] innerHashCodes = new int[array.size() * 2]; @@ -211,9 +217,9 @@ public final class BundleUtil { innerHashCodes[j * 2 + 1] = item.hashCode(); } } - hashCodes[i++] = Arrays.hashCode(innerHashCodes); + hashCodes[hashCodeIdx++] = Arrays.hashCode(innerHashCodes); } else { - hashCodes[i++] = value.hashCode(); + hashCodes[hashCodeIdx++] = value.hashCode(); } } return Arrays.hashCode(hashCodes); diff --git a/apex/appsearch/synced_jetpack_changeid.txt b/apex/appsearch/synced_jetpack_changeid.txt index 12699b7815fb..6ba557224582 100644 --- a/apex/appsearch/synced_jetpack_changeid.txt +++ b/apex/appsearch/synced_jetpack_changeid.txt @@ -1 +1 @@ -Ibe06fb9c574c8718191f833bb042fa10c300e4e2 +I2bf8bd9db1b71b7da4ab50dd7480e4529678413a diff --git a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java index 20fb90986f41..44d5180c3a36 100644 --- a/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java +++ b/apex/appsearch/testing/java/com/android/server/appsearch/testing/external/AppSearchTestUtils.java @@ -59,7 +59,7 @@ public class AppSearchTestUtils { session.getByUri( new GetByUriRequest.Builder() .setNamespace(namespace) - .addUri(uris) + .addUris(uris) .build())); assertThat(result.getSuccesses()).hasSize(uris.length); assertThat(result.getFailures()).isEmpty(); diff --git a/core/api/current.txt b/core/api/current.txt index 838dd55e34d5..916d25ec4f81 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -435,6 +435,7 @@ package android { field public static final int clickable = 16842981; // 0x10100e5 field public static final int clipChildren = 16842986; // 0x10100ea field public static final int clipOrientation = 16843274; // 0x101020a + field public static final int clipToOutline = 16844328; // 0x1010628 field public static final int clipToPadding = 16842987; // 0x10100eb field public static final int closeIcon = 16843905; // 0x1010481 field @Deprecated public static final int codes = 16843330; // 0x1010242 @@ -2936,12 +2937,12 @@ package android.accessibilityservice { field public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20; // 0x14 field public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40; // 0x28 field public static final int GESTURE_2_FINGER_SINGLE_TAP = 19; // 0x13 - field public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43; // 0x2b field public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26; // 0x1a field public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27; // 0x1b field public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28; // 0x1c field public static final int GESTURE_2_FINGER_SWIPE_UP = 25; // 0x19 field public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21; // 0x15 + field public static final int GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD = 43; // 0x2b field public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23; // 0x17 field public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; // 0x29 field public static final int GESTURE_3_FINGER_SINGLE_TAP = 22; // 0x16 @@ -40338,6 +40339,7 @@ package android.telephony { field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool"; field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool"; field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool"; + field public static final String KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL = "store_sim_pin_for_unattended_reboot_bool"; field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool"; field public static final String KEY_SUPPORT_3GPP_CALL_FORWARDING_WHILE_ROAMING_BOOL = "support_3gpp_call_forwarding_while_roaming_bool"; field public static final String KEY_SUPPORT_ADD_CONFERENCE_PARTICIPANTS_BOOL = "support_add_conference_participants_bool"; @@ -50687,6 +50689,7 @@ package android.view.autofill { method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback); field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE"; field public static final String EXTRA_AUTHENTICATION_RESULT = "android.view.autofill.extra.AUTHENTICATION_RESULT"; + field public static final String EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET = "android.view.autofill.extra.AUTHENTICATION_RESULT_EPHEMERAL_DATASET"; field public static final String EXTRA_CLIENT_STATE = "android.view.autofill.extra.CLIENT_STATE"; } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 5bb3e0517032..058e5a872b6a 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -11869,6 +11869,7 @@ package android.telephony { method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean matchesCurrentSimOperator(@NonNull String, int, @Nullable String); method public boolean needsOtaServiceProvisioning(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyOtaEmergencyNumberDbInstalled(); + method @RequiresPermission(android.Manifest.permission.REBOOT) public int prepareForUnattendedReboot(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio(); method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void reportDefaultNetworkStatus(boolean); method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); @@ -11993,6 +11994,9 @@ package android.telephony { field public static final int NR_DUAL_CONNECTIVITY_DISABLE = 2; // 0x2 field public static final int NR_DUAL_CONNECTIVITY_DISABLE_IMMEDIATE = 3; // 0x3 field public static final int NR_DUAL_CONNECTIVITY_ENABLE = 1; // 0x1 + field public static final int PREPARE_UNATTENDED_REBOOT_ERROR = 2; // 0x2 + field public static final int PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED = 1; // 0x1 + field public static final int PREPARE_UNATTENDED_REBOOT_SUCCESS = 0; // 0x0 field public static final int RADIO_POWER_OFF = 0; // 0x0 field public static final int RADIO_POWER_ON = 1; // 0x1 field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2 diff --git a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java index 4b2d74160006..768ec3851991 100644 --- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java +++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java @@ -20,12 +20,12 @@ package android.accessibilityservice; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP; -import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP; @@ -97,10 +97,10 @@ public final class AccessibilityGestureEvent implements Parcelable { GESTURE_UNKNOWN, GESTURE_TOUCH_EXPLORATION, GESTURE_2_FINGER_SINGLE_TAP, - GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD, GESTURE_2_FINGER_DOUBLE_TAP, GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, GESTURE_2_FINGER_TRIPLE_TAP, + GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD, GESTURE_3_FINGER_SINGLE_TAP, GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD, GESTURE_3_FINGER_DOUBLE_TAP, @@ -232,8 +232,8 @@ public final class AccessibilityGestureEvent implements Parcelable { case GESTURE_PASSTHROUGH: return "GESTURE_PASSTHROUGH"; case GESTURE_TOUCH_EXPLORATION: return "GESTURE_TOUCH_EXPLORATION"; case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP"; - case GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD: - return "GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD"; + case GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD: + return "GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD"; case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP"; case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD: return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD"; diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java index 1fa7fa2b744f..dab4a5dc316c 100644 --- a/core/java/android/accessibilityservice/AccessibilityService.java +++ b/core/java/android/accessibilityservice/AccessibilityService.java @@ -445,8 +445,8 @@ public abstract class AccessibilityService extends Service { /** The user has performed a three-finger double tap and hold gesture on the touch screen. */ public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41; - /** The user has performed a two-finger single-tap and hold gesture on the touch screen. */ - public static final int GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD = 43; + /** The user has performed a two-finger triple-tap and hold gesture on the touch screen. */ + public static final int GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD = 43; /** The user has performed a three-finger single-tap and hold gesture on the touch screen. */ public static final int GESTURE_3_FINGER_SINGLE_TAP_AND_HOLD = 44; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 0774ac134d1b..bb6a774cbee2 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2289,11 +2289,12 @@ public final class ActivityThread extends ClientTransactionHandler { * Creates the top level resources for the given package. Will return an existing * Resources if one has already been created. */ - Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, - String[] libDirs, LoadedApk pkgInfo, Configuration overrideConfig) { - return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs, - null, overrideConfig, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(), - null); + Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] legacyOverlayDirs, + String[] overlayPaths, String[] libDirs, LoadedApk pkgInfo, + Configuration overrideConfig) { + return mResourcesManager.getResources(null, resDir, splitResDirs, legacyOverlayDirs, + overlayPaths, libDirs, null, overrideConfig, pkgInfo.getCompatibilityInfo(), + pkgInfo.getClassLoader(), null); } @UnsupportedAppUsage @@ -2462,12 +2463,15 @@ public final class ActivityThread extends ClientTransactionHandler { private static boolean isLoadedApkResourceDirsUpToDate(LoadedApk loadedApk, ApplicationInfo appInfo) { Resources packageResources = loadedApk.mResources; - String[] overlayDirs = ArrayUtils.defeatNullable(loadedApk.getOverlayDirs()); - String[] resourceDirs = ArrayUtils.defeatNullable(appInfo.resourceDirs); + boolean resourceDirsUpToDate = Arrays.equals( + ArrayUtils.defeatNullable(appInfo.resourceDirs), + ArrayUtils.defeatNullable(loadedApk.getOverlayDirs())); + boolean overlayPathsUpToDate = Arrays.equals( + ArrayUtils.defeatNullable(appInfo.overlayPaths), + ArrayUtils.defeatNullable(loadedApk.getOverlayPaths())); return (packageResources == null || packageResources.getAssets().isUpToDate()) - && overlayDirs.length == resourceDirs.length - && ArrayUtils.containsAll(overlayDirs, resourceDirs); + && resourceDirsUpToDate && overlayPathsUpToDate; } @UnsupportedAppUsage diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 8ac91396a6b0..062cab457ebe 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -1728,7 +1728,7 @@ public class ApplicationPackageManager extends PackageManager { final Resources r = mContext.mMainThread.getTopLevelResources( sameUid ? app.sourceDir : app.publicSourceDir, sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, - app.resourceDirs, app.sharedLibraryFiles, + app.resourceDirs, app.overlayPaths, app.sharedLibraryFiles, mContext.mPackageInfo, configuration); if (r != null) { return r; diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 4ddeb8fbfbef..9a20e0fefd33 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -2345,6 +2345,7 @@ class ContextImpl extends Context { pi.getResDir(), splitResDirs, pi.getOverlayDirs(), + pi.getOverlayPaths(), pi.getApplicationInfo().sharedLibraryFiles, overrideDisplayId, overrideConfig, @@ -2442,6 +2443,7 @@ class ContextImpl extends Context { mPackageInfo.getResDir(), paths, mPackageInfo.getOverlayDirs(), + mPackageInfo.getOverlayPaths(), mPackageInfo.getApplicationInfo().sharedLibraryFiles, mForceDisplayOverrideInResources ? getDisplayId() : null, null, @@ -2558,7 +2560,8 @@ class ContextImpl extends Context { Resources createWindowContextResources() { final String resDir = mPackageInfo.getResDir(); final String[] splitResDirs = mPackageInfo.getSplitResDirs(); - final String[] overlayDirs = mPackageInfo.getOverlayDirs(); + final String[] legacyOverlayDirs = mPackageInfo.getOverlayDirs(); + final String[] overlayPaths = mPackageInfo.getOverlayPaths(); final String[] libDirs = mPackageInfo.getApplicationInfo().sharedLibraryFiles; final int displayId = getDisplayId(); final CompatibilityInfo compatInfo = (displayId == Display.DEFAULT_DISPLAY) @@ -2567,7 +2570,7 @@ class ContextImpl extends Context { final List<ResourcesLoader> loaders = mResources.getLoaders(); return mResourcesManager.createBaseTokenResources(mToken, resDir, splitResDirs, - overlayDirs, libDirs, displayId, null /* overrideConfig */, + legacyOverlayDirs, overlayPaths, libDirs, displayId, null /* overrideConfig */, compatInfo, mClassLoader, loaders); } @@ -2855,6 +2858,7 @@ class ContextImpl extends Context { packageInfo.getResDir(), splitDirs, packageInfo.getOverlayDirs(), + packageInfo.getOverlayPaths(), packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, overrideConfiguration, diff --git a/core/java/android/app/GameManager.java b/core/java/android/app/GameManager.java index 8b6570f1241f..ac1fa1ec6837 100644 --- a/core/java/android/app/GameManager.java +++ b/core/java/android/app/GameManager.java @@ -16,7 +16,9 @@ package android.app; +import android.Manifest; import android.annotation.IntDef; +import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.UserHandleAware; import android.content.Context; @@ -73,8 +75,8 @@ public final class GameManager { /** * Returns the game mode for the given package. */ - // TODO(b/178111358): Add @RequiresPermission. @UserHandleAware + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public @GameMode int getGameMode(String packageName) { try { return mService.getGameMode(packageName, mContext.getUserId()); @@ -86,8 +88,8 @@ public final class GameManager { /** * Sets the game mode for the given package. */ - // TODO(b/178111358): Add @RequiresPermission. @UserHandleAware + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(String packageName, @GameMode int gameMode) { try { mService.setGameMode(packageName, gameMode, mContext.getUserId()); diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index c01b5a32b98b..be426aa7ed2b 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -113,7 +113,8 @@ public final class LoadedApk { private String mAppDir; @UnsupportedAppUsage private String mResDir; - private String[] mOverlayDirs; + private String[] mLegacyOverlayDirs; + private String[] mOverlayPaths; @UnsupportedAppUsage private String mDataDir; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @@ -222,7 +223,8 @@ public final class LoadedApk { mSplitAppDirs = null; mSplitResDirs = null; mSplitClassLoaderNames = null; - mOverlayDirs = null; + mLegacyOverlayDirs = null; + mOverlayPaths = null; mDataDir = null; mDataDirFile = null; mDeviceProtectedDataDirFile = null; @@ -364,8 +366,8 @@ public final class LoadedApk { } mResources = ResourcesManager.getInstance().getResources(null, mResDir, - splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, - null, null, getCompatibilityInfo(), + splitPaths, mLegacyOverlayDirs, mOverlayPaths, + mApplicationInfo.sharedLibraryFiles, null, null, getCompatibilityInfo(), getClassLoader(), mApplication == null ? null : mApplication.getResources().getLoaders()); } @@ -379,7 +381,8 @@ public final class LoadedApk { mApplicationInfo = aInfo; mAppDir = aInfo.sourceDir; mResDir = aInfo.uid == myUid ? aInfo.sourceDir : aInfo.publicSourceDir; - mOverlayDirs = aInfo.resourceDirs; + mLegacyOverlayDirs = aInfo.resourceDirs; + mOverlayPaths = aInfo.overlayPaths; mDataDir = aInfo.dataDir; mLibDir = aInfo.nativeLibraryDir; mDataDirFile = FileUtils.newFileOrNull(aInfo.dataDir); @@ -1213,9 +1216,19 @@ public final class LoadedApk { return mSplitResDirs; } + /** + * Corresponds to {@link ApplicationInfo#resourceDirs}. + */ @UnsupportedAppUsage public String[] getOverlayDirs() { - return mOverlayDirs; + return mLegacyOverlayDirs; + } + + /** + * Corresponds to {@link ApplicationInfo#overlayPaths}. + */ + public String[] getOverlayPaths() { + return mOverlayPaths; } public String getDataDir() { @@ -1252,8 +1265,8 @@ public final class LoadedApk { } mResources = ResourcesManager.getInstance().getResources(null, mResDir, - splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, - null, null, getCompatibilityInfo(), + splitPaths, mLegacyOverlayDirs, mOverlayPaths, + mApplicationInfo.sharedLibraryFiles, null, null, getCompatibilityInfo(), getClassLoader(), null); } return mResources; diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 772833cc6d2d..ac8d3a261ac6 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -39,6 +39,7 @@ import android.os.IBinder; import android.os.Process; import android.os.Trace; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; @@ -60,6 +61,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.WeakHashMap; @@ -174,8 +176,8 @@ public class ResourcesManager { * based on. * * @see #activityResources - * @see #getResources(IBinder, String, String[], String[], String[], Integer, Configuration, - * CompatibilityInfo, ClassLoader, List) + * @see #getResources(IBinder, String, String[], String[], String[], String[], Integer, + * Configuration, CompatibilityInfo, ClassLoader, List) */ public final Configuration overrideConfig = new Configuration(); @@ -482,8 +484,8 @@ public class ResourcesManager { } } - if (key.mOverlayDirs != null) { - for (final String idmapPath : key.mOverlayDirs) { + if (key.mOverlayPaths != null) { + for (final String idmapPath : key.mOverlayPaths) { apkKeys.add(new ApkKey(idmapPath, false /*sharedLib*/, true /*overlay*/)); } } @@ -783,14 +785,16 @@ public class ResourcesManager { /** * Creates base resources for a binder token. Calls to - * {@link #getResources(IBinder, String, String[], String[], String[], Integer, Configuration, - * CompatibilityInfo, ClassLoader, List)} with the same binder token will have their override - * configurations merged with the one specified here. + * + * {@link #getResources(IBinder, String, String[], String[], String[], String[], Integer, + * Configuration, CompatibilityInfo, ClassLoader, List)} with the same binder token will have + * their override configurations merged with the one specified here. * * @param token Represents an {@link Activity} or {@link WindowContext}. * @param resDir The base resource path. Can be null (only framework resources will be loaded). * @param splitResDirs An array of split resource paths. Can be null. - * @param overlayDirs An array of overlay paths. Can be null. + * @param legacyOverlayDirs An array of overlay APK paths. Can be null. + * @param overlayPaths An array of overlay APK and non-APK paths. Can be null. * @param libDirs An array of resource library paths. Can be null. * @param displayId The ID of the display for which to create the resources. * @param overrideConfig The configuration to apply on top of the base configuration. Can be @@ -804,7 +808,8 @@ public class ResourcesManager { public @Nullable Resources createBaseTokenResources(@NonNull IBinder token, @Nullable String resDir, @Nullable String[] splitResDirs, - @Nullable String[] overlayDirs, + @Nullable String[] legacyOverlayDirs, + @Nullable String[] overlayPaths, @Nullable String[] libDirs, int displayId, @Nullable Configuration overrideConfig, @@ -817,7 +822,7 @@ public class ResourcesManager { final ResourcesKey key = new ResourcesKey( resDir, splitResDirs, - overlayDirs, + combinedOverlayPaths(legacyOverlayDirs, overlayPaths), libDirs, displayId, overrideConfig, @@ -1043,7 +1048,8 @@ public class ResourcesManager { * @param activityToken Represents an Activity. If null, global resources are assumed. * @param resDir The base resource path. Can be null (only framework resources will be loaded). * @param splitResDirs An array of split resource paths. Can be null. - * @param overlayDirs An array of overlay paths. Can be null. + * @param legacyOverlayDirs An array of overlay APK paths. Can be null. + * @param overlayPaths An array of overlay APK and non-APK paths. Can be null. * @param libDirs An array of resource library paths. Can be null. * @param overrideDisplayId The ID of the display for which the returned Resources should be * based. This will cause display-based configuration properties to override those of the base @@ -1063,7 +1069,8 @@ public class ResourcesManager { @Nullable IBinder activityToken, @Nullable String resDir, @Nullable String[] splitResDirs, - @Nullable String[] overlayDirs, + @Nullable String[] legacyOverlayDirs, + @Nullable String[] overlayPaths, @Nullable String[] libDirs, @Nullable Integer overrideDisplayId, @Nullable Configuration overrideConfig, @@ -1075,7 +1082,7 @@ public class ResourcesManager { final ResourcesKey key = new ResourcesKey( resDir, splitResDirs, - overlayDirs, + combinedOverlayPaths(legacyOverlayDirs, overlayPaths), libDirs, overrideDisplayId != null ? overrideDisplayId : INVALID_DISPLAY, overrideConfig, @@ -1250,7 +1257,7 @@ public class ResourcesManager { // Create the new ResourcesKey with the rebased override config. final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir, - oldKey.mSplitResDirs, oldKey.mOverlayDirs, oldKey.mLibDirs, + oldKey.mSplitResDirs, oldKey.mOverlayPaths, oldKey.mLibDirs, displayId, rebasedOverrideConfig, oldKey.mCompatInfo, oldKey.mLoaders); if (DEBUG) { @@ -1393,7 +1400,7 @@ public class ResourcesManager { updatedResourceKeys.put(impl, new ResourcesKey( key.mResDir, key.mSplitResDirs, - key.mOverlayDirs, + key.mOverlayPaths, newLibAssets, key.mDisplayId, key.mOverrideConfiguration, @@ -1423,7 +1430,8 @@ public class ResourcesManager { // ApplicationInfo is mutable, so clone the arrays to prevent outside modification String[] copiedSplitDirs = ArrayUtils.cloneOrNull(newSplitDirs); - String[] copiedResourceDirs = ArrayUtils.cloneOrNull(appInfo.resourceDirs); + String[] copiedResourceDirs = combinedOverlayPaths(appInfo.resourceDirs, + appInfo.overlayPaths); final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); final int implCount = mResourceImpls.size(); @@ -1458,6 +1466,39 @@ public class ResourcesManager { } } + /** + * Creates an array with the contents of {@param overlayPaths} and the unique elements of + * {@param resourceDirs}. + * + * {@link ApplicationInfo#resourceDirs} only contains paths of overlays APKs. + * {@link ApplicationInfo#overlayPaths} was created to contain paths of overlay of varying file + * formats. It also contains the contents of {@code resourceDirs} because the order of loaded + * overlays matter. In case {@code resourceDirs} contains overlay APK paths that are not present + * in overlayPaths (perhaps an app inserted an additional overlay path into a + * {@code resourceDirs}), this method is used to combine the contents of {@code resourceDirs} + * that do not exist in {@code overlayPaths}} and {@code overlayPaths}}. + */ + @Nullable + private static String[] combinedOverlayPaths(@Nullable String[] resourceDirs, + @Nullable String[] overlayPaths) { + if (resourceDirs == null) { + return ArrayUtils.cloneOrNull(overlayPaths); + } else if(overlayPaths == null) { + return ArrayUtils.cloneOrNull(resourceDirs); + } else { + final ArrayList<String> paths = new ArrayList<>(); + for (final String path : overlayPaths) { + paths.add(path); + } + for (final String path : resourceDirs) { + if (!paths.contains(path)) { + paths.add(path); + } + } + return paths.toArray(new String[0]); + } + } + private void redirectResourcesToNewImplLocked( @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) { // Bail early if there is no work to do. @@ -1559,7 +1600,7 @@ public class ResourcesManager { final ResourcesKey newKey = new ResourcesKey( oldKey.mResDir, oldKey.mSplitResDirs, - oldKey.mOverlayDirs, + oldKey.mOverlayPaths, oldKey.mLibDirs, oldKey.mDisplayId, oldKey.mOverrideConfiguration, diff --git a/core/java/android/app/usage/IUsageStatsManager.aidl b/core/java/android/app/usage/IUsageStatsManager.aidl index 2c1e951b6128..30ea5c476191 100644 --- a/core/java/android/app/usage/IUsageStatsManager.aidl +++ b/core/java/android/app/usage/IUsageStatsManager.aidl @@ -30,7 +30,7 @@ import java.util.Map; interface IUsageStatsManager { @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) ParceledListSlice queryUsageStats(int bucketType, long beginTime, long endTime, - String callingPackage); + String callingPackage, int userId); @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) ParceledListSlice queryConfigurationStats(int bucketType, long beginTime, long endTime, String callingPackage); diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java index f74d16ee9238..31781ec79203 100644 --- a/core/java/android/app/usage/UsageStatsManager.java +++ b/core/java/android/app/usage/UsageStatsManager.java @@ -23,6 +23,7 @@ import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.UserHandleAware; import android.app.Activity; import android.app.PendingIntent; import android.compat.annotation.UnsupportedAppUsage; @@ -437,11 +438,12 @@ public final class UsageStatsManager { * @see #INTERVAL_YEARLY * @see #INTERVAL_BEST */ + @UserHandleAware public List<UsageStats> queryUsageStats(int intervalType, long beginTime, long endTime) { try { @SuppressWarnings("unchecked") ParceledListSlice<UsageStats> slice = mService.queryUsageStats(intervalType, beginTime, - endTime, mContext.getOpPackageName()); + endTime, mContext.getOpPackageName(), mContext.getUserId()); if (slice != null) { return slice.getList(); } diff --git a/core/java/android/companion/Association.java b/core/java/android/companion/Association.java index 960a08755cb8..9007d9d8bbcc 100644 --- a/core/java/android/companion/Association.java +++ b/core/java/android/companion/Association.java @@ -23,6 +23,7 @@ import android.os.Parcelable; import com.android.internal.util.DataClass; +import java.util.Date; import java.util.Objects; /** @@ -39,12 +40,19 @@ public final class Association implements Parcelable { private final @NonNull String mPackageName; private final @Nullable String mDeviceProfile; private final boolean mNotifyOnDeviceNearby; + private final long mTimeApprovedMs; /** @hide */ public int getUserId() { return mUserId; } + private String timeApprovedMsToString() { + return new Date(mTimeApprovedMs).toString(); + } + + + // Code below generated by codegen v1.0.22. @@ -71,7 +79,8 @@ public final class Association implements Parcelable { @NonNull String deviceMacAddress, @NonNull String packageName, @Nullable String deviceProfile, - boolean notifyOnDeviceNearby) { + boolean notifyOnDeviceNearby, + long timeApprovedMs) { this.mUserId = userId; com.android.internal.util.AnnotationValidations.validate( UserIdInt.class, null, mUserId); @@ -83,6 +92,7 @@ public final class Association implements Parcelable { NonNull.class, null, mPackageName); this.mDeviceProfile = deviceProfile; this.mNotifyOnDeviceNearby = notifyOnDeviceNearby; + this.mTimeApprovedMs = timeApprovedMs; // onConstructed(); // You can define this method to get a callback } @@ -107,6 +117,11 @@ public final class Association implements Parcelable { return mNotifyOnDeviceNearby; } + @DataClass.Generated.Member + public long getTimeApprovedMs() { + return mTimeApprovedMs; + } + @Override @DataClass.Generated.Member public String toString() { @@ -118,7 +133,8 @@ public final class Association implements Parcelable { "deviceMacAddress = " + mDeviceMacAddress + ", " + "packageName = " + mPackageName + ", " + "deviceProfile = " + mDeviceProfile + ", " + - "notifyOnDeviceNearby = " + mNotifyOnDeviceNearby + + "notifyOnDeviceNearby = " + mNotifyOnDeviceNearby + ", " + + "timeApprovedMs = " + timeApprovedMsToString() + " }"; } @@ -139,7 +155,8 @@ public final class Association implements Parcelable { && Objects.equals(mDeviceMacAddress, that.mDeviceMacAddress) && Objects.equals(mPackageName, that.mPackageName) && Objects.equals(mDeviceProfile, that.mDeviceProfile) - && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby; + && mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby + && mTimeApprovedMs == that.mTimeApprovedMs; } @Override @@ -154,6 +171,7 @@ public final class Association implements Parcelable { _hash = 31 * _hash + Objects.hashCode(mPackageName); _hash = 31 * _hash + Objects.hashCode(mDeviceProfile); _hash = 31 * _hash + Boolean.hashCode(mNotifyOnDeviceNearby); + _hash = 31 * _hash + Long.hashCode(mTimeApprovedMs); return _hash; } @@ -171,6 +189,7 @@ public final class Association implements Parcelable { dest.writeString(mDeviceMacAddress); dest.writeString(mPackageName); if (mDeviceProfile != null) dest.writeString(mDeviceProfile); + dest.writeLong(mTimeApprovedMs); } @Override @@ -190,6 +209,7 @@ public final class Association implements Parcelable { String deviceMacAddress = in.readString(); String packageName = in.readString(); String deviceProfile = (flg & 0x8) == 0 ? null : in.readString(); + long timeApprovedMs = in.readLong(); this.mUserId = userId; com.android.internal.util.AnnotationValidations.validate( @@ -202,6 +222,7 @@ public final class Association implements Parcelable { NonNull.class, null, mPackageName); this.mDeviceProfile = deviceProfile; this.mNotifyOnDeviceNearby = notifyOnDeviceNearby; + this.mTimeApprovedMs = timeApprovedMs; // onConstructed(); // You can define this method to get a callback } @@ -221,10 +242,10 @@ public final class Association implements Parcelable { }; @DataClass.Generated( - time = 1610482674799L, + time = 1612832377589L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/companion/Association.java", - inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final boolean mNotifyOnDeviceNearby\npublic int getUserId()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)") + inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull java.lang.String mDeviceMacAddress\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.Nullable java.lang.String mDeviceProfile\nprivate final boolean mNotifyOnDeviceNearby\nprivate final long mTimeApprovedMs\npublic int getUserId()\nprivate java.lang.String timeApprovedMsToString()\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl index 527d8df94ea0..95d3515abb80 100644 --- a/core/java/android/companion/ICompanionDeviceManager.aidl +++ b/core/java/android/companion/ICompanionDeviceManager.aidl @@ -49,4 +49,6 @@ interface ICompanionDeviceManager { void registerDevicePresenceListenerService(in String packageName, in String deviceAddress); void unregisterDevicePresenceListenerService(in String packageName, in String deviceAddress); + + boolean canPairWithoutPrompt(in String packageName, in String deviceMacAddress, int userId); } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 6ec11693d69b..01ff4326a800 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -923,6 +923,14 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public String[] resourceDirs; /** + * Contains the contents of {@link #resourceDirs} and along with paths for overlays that may or + * may not be APK packages. + * + * {@hide} + */ + public String[] overlayPaths; + + /** * String retrieved from the seinfo tag found in selinux policy. This value can be set through * the mac_permissions.xml policy construct. This value is used for setting an SELinux security * context on the process as well as its data directory. @@ -1472,6 +1480,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { if (resourceDirs != null) { pw.println(prefix + "resourceDirs=" + Arrays.toString(resourceDirs)); } + if (overlayPaths != null) { + pw.println(prefix + "overlayPaths=" + Arrays.toString(overlayPaths)); + } if ((dumpFlags & DUMP_FLAG_DETAILS) != 0 && seInfo != null) { pw.println(prefix + "seinfo=" + seInfo); pw.println(prefix + "seinfoUser=" + seInfoUser); @@ -1568,6 +1579,11 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { proto.write(ApplicationInfoProto.RESOURCE_DIRS, dir); } } + if (overlayPaths != null) { + for (String dir : overlayPaths) { + proto.write(ApplicationInfoProto.OVERLAY_PATHS, dir); + } + } proto.write(ApplicationInfoProto.DATA_DIR, dataDir); proto.write(ApplicationInfoProto.CLASS_LOADER_NAME, classLoaderName); if (!ArrayUtils.isEmpty(splitClassLoaderNames)) { @@ -1717,6 +1733,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { primaryCpuAbi = orig.primaryCpuAbi; secondaryCpuAbi = orig.secondaryCpuAbi; resourceDirs = orig.resourceDirs; + overlayPaths = orig.overlayPaths; seInfo = orig.seInfo; seInfoUser = orig.seInfoUser; sharedLibraryFiles = orig.sharedLibraryFiles; @@ -1803,6 +1820,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeString8(primaryCpuAbi); dest.writeString8(secondaryCpuAbi); dest.writeString8Array(resourceDirs); + dest.writeString8Array(overlayPaths); dest.writeString8(seInfo); dest.writeString8(seInfoUser); dest.writeString8Array(sharedLibraryFiles); @@ -1886,6 +1904,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { primaryCpuAbi = source.readString8(); secondaryCpuAbi = source.readString8(); resourceDirs = source.createString8Array(); + overlayPaths = source.createString8Array(); seInfo = source.readString8(); seInfoUser = source.readString8(); sharedLibraryFiles = source.createString8Array(); @@ -2282,7 +2301,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * @hide */ public String[] getAllApkPaths() { - final String[][] inputLists = { splitSourceDirs, sharedLibraryFiles, resourceDirs }; + final String[][] inputLists = { + splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths + }; final List<String> output = new ArrayList<>(10); if (sourceDir != null) { output.add(sourceDir); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index e6c0f6a4c2fa..0819d1743ad6 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -54,6 +54,7 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.overlay.OverlayPaths; import android.content.pm.split.SplitAssetLoader; import android.content.res.ApkAssets; import android.content.res.AssetManager; @@ -7969,7 +7970,11 @@ public class PackageParser { ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName); } ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state); - ai.resourceDirs = state.getAllOverlayPaths(); + final OverlayPaths overlayPaths = state.getAllOverlayPaths(); + if (overlayPaths != null) { + ai.resourceDirs = overlayPaths.getResourceDirs().toArray(new String[0]); + ai.overlayPaths = overlayPaths.getOverlayPaths().toArray(new String[0]); + } ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes; } @@ -8600,6 +8605,7 @@ public class PackageParser { null, null, androidAppInfo.resourceDirs, + androidAppInfo.overlayPaths, androidAppInfo.sharedLibraryFiles, null, null, diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java index 5cc74c0a1c8e..e115597865b3 100644 --- a/core/java/android/content/pm/PackageUserState.java +++ b/core/java/android/content/pm/PackageUserState.java @@ -31,6 +31,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; +import android.content.pm.overlay.OverlayPaths; import android.content.pm.parsing.component.ParsedMainComponent; import android.os.BaseBundle; import android.os.Debug; @@ -53,7 +54,6 @@ import org.xmlpull.v1.XmlSerializer; import java.io.IOException; import java.util.Arrays; -import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; @@ -85,9 +85,10 @@ public class PackageUserState { public ArraySet<String> disabledComponents; public ArraySet<String> enabledComponents; - private String[] overlayPaths; - private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths - private String[] cachedOverlayPaths; + private OverlayPaths overlayPaths; + // Maps library name to overlay paths. + private ArrayMap<String, OverlayPaths> sharedLibraryOverlayPaths; + private OverlayPaths cachedOverlayPaths; @Nullable private ArrayMap<ComponentName, Pair<String, Integer>> componentLabelIconOverrideMap; @@ -121,8 +122,7 @@ public class PackageUserState { uninstallReason = o.uninstallReason; disabledComponents = ArrayUtils.cloneOrNull(o.disabledComponents); enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents); - overlayPaths = - o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length); + overlayPaths = o.overlayPaths; if (o.sharedLibraryOverlayPaths != null) { sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths); } @@ -132,25 +132,55 @@ public class PackageUserState { } } - public String[] getOverlayPaths() { + @Nullable + public OverlayPaths getOverlayPaths() { return overlayPaths; } - public void setOverlayPaths(String[] paths) { - overlayPaths = paths; - cachedOverlayPaths = null; + @Nullable + public Map<String, OverlayPaths> getSharedLibraryOverlayPaths() { + return sharedLibraryOverlayPaths; } - public Map<String, String[]> getSharedLibraryOverlayPaths() { - return sharedLibraryOverlayPaths; + /** + * Sets the path of overlays currently enabled for this package and user combination. + * @return true if the path contents differ than what they were previously + */ + @Nullable + public boolean setOverlayPaths(@Nullable OverlayPaths paths) { + if (Objects.equals(paths, overlayPaths)) { + return false; + } + if ((overlayPaths == null && paths.isEmpty()) + || (paths == null && overlayPaths.isEmpty())) { + return false; + } + overlayPaths = paths; + cachedOverlayPaths = null; + return true; } - public void setSharedLibraryOverlayPaths(String library, String[] paths) { + /** + * Sets the path of overlays currently enabled for a library that this package uses. + * + * @return true if the path contents for the library differ than what they were previously + */ + public boolean setSharedLibraryOverlayPaths(@NonNull String library, + @Nullable OverlayPaths paths) { if (sharedLibraryOverlayPaths == null) { sharedLibraryOverlayPaths = new ArrayMap<>(); } - sharedLibraryOverlayPaths.put(library, paths); + final OverlayPaths currentPaths = sharedLibraryOverlayPaths.get(library); + if (Objects.equals(paths, currentPaths)) { + return false; + } cachedOverlayPaths = null; + if (paths == null || paths.isEmpty()) { + return sharedLibraryOverlayPaths.remove(library) != null; + } else { + sharedLibraryOverlayPaths.put(library, paths); + return true; + } } /** @@ -332,35 +362,21 @@ public class PackageUserState { return isComponentEnabled; } - public String[] getAllOverlayPaths() { + public OverlayPaths getAllOverlayPaths() { if (overlayPaths == null && sharedLibraryOverlayPaths == null) { return null; } - if (cachedOverlayPaths != null) { return cachedOverlayPaths; } - - final LinkedHashSet<String> paths = new LinkedHashSet<>(); - if (overlayPaths != null) { - final int N = overlayPaths.length; - for (int i = 0; i < N; i++) { - paths.add(overlayPaths[i]); - } - } - + final OverlayPaths.Builder newPaths = new OverlayPaths.Builder(); + newPaths.addAll(overlayPaths); if (sharedLibraryOverlayPaths != null) { - for (String[] libOverlayPaths : sharedLibraryOverlayPaths.values()) { - if (libOverlayPaths != null) { - final int N = libOverlayPaths.length; - for (int i = 0; i < N; i++) { - paths.add(libOverlayPaths[i]); - } - } + for (final OverlayPaths libOverlayPaths : sharedLibraryOverlayPaths.values()) { + newPaths.addAll(libOverlayPaths); } } - - cachedOverlayPaths = paths.toArray(new String[0]); + cachedOverlayPaths = newPaths.build(); return cachedOverlayPaths; } diff --git a/core/java/android/content/pm/overlay/OverlayPaths.java b/core/java/android/content/pm/overlay/OverlayPaths.java new file mode 100644 index 000000000000..a4db733af013 --- /dev/null +++ b/core/java/android/content/pm/overlay/OverlayPaths.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2021 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.content.pm.overlay; + +import android.annotation.NonNull; +import android.annotation.Nullable; + +import com.android.internal.util.DataClass; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** @hide */ +@DataClass(genConstructor = false, genBuilder = false, genHiddenBuilder = false, + genEqualsHashCode = true, genToString = true) +public class OverlayPaths { + /** + * Represents {@link android.content.pm.ApplicationInfo#resourceDirs}. + * Only contains paths to APKs of overlays that can have their idmap resolved from their base + * APK path. Currently all overlay APKs can have their idmap path resolved from their idmap + * path. + */ + @NonNull + private final List<String> mResourceDirs = new ArrayList<>(); + + /** + * Represents {@link android.content.pm.ApplicationInfo#overlayPaths}. + * Contains the contents of {@link #getResourceDirs()} and along with paths for overlays + * that are not APKs. + */ + @NonNull + private final List<String> mOverlayPaths = new ArrayList<>(); + + public static class Builder { + final OverlayPaths mPaths = new OverlayPaths(); + + /** + * Adds a non-APK path to the contents of {@link OverlayPaths#getOverlayPaths()}. + */ + public Builder addNonApkPath(@NonNull String idmapPath) { + mPaths.mOverlayPaths.add(idmapPath); + return this; + } + + /** + * Adds a overlay APK path to the contents of {@link OverlayPaths#getResourceDirs()} and + * {@link OverlayPaths#getOverlayPaths()}. + */ + public Builder addApkPath(@NonNull String overlayPath) { + addUniquePath(mPaths.mResourceDirs, overlayPath); + addUniquePath(mPaths.mOverlayPaths, overlayPath); + return this; + } + + public Builder addAll(@Nullable OverlayPaths other) { + if (other != null) { + for (final String path : other.getResourceDirs()) { + addUniquePath(mPaths.mResourceDirs, path); + } + for (final String path : other.getOverlayPaths()) { + addUniquePath(mPaths.mOverlayPaths, path); + } + } + return this; + } + + public OverlayPaths build() { + return mPaths; + } + + private static void addUniquePath(@NonNull List<String> paths, @NonNull String path) { + if (!paths.contains(path)) { + paths.add(path); + } + } + } + + /** + * Returns whether {@link #getOverlayPaths()} and {@link #getOverlayPaths} are empty. + */ + public boolean isEmpty() { + return mResourceDirs.isEmpty() && mOverlayPaths.isEmpty(); + } + + private OverlayPaths() { + } + + + + // Code below generated by codegen v1.0.22. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/overlay/OverlayPaths.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + /** + * Represents {@link android.content.pm.ApplicationInfo#resourceDirs}. + * Only contains paths to APKs of overlays that can have their idmap resolved from their base + * APK path. Currently all overlay APKs can have their idmap path resolved from their idmap + * path. + */ + @DataClass.Generated.Member + public @NonNull List<String> getResourceDirs() { + return mResourceDirs; + } + + /** + * Represents {@link android.content.pm.ApplicationInfo#overlayPaths}. + * Contains the contents of {@link #getResourceDirs()} and along with paths for overlays + * that are not APKs. + */ + @DataClass.Generated.Member + public @NonNull List<String> getOverlayPaths() { + return mOverlayPaths; + } + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "OverlayPaths { " + + "resourceDirs = " + mResourceDirs + ", " + + "overlayPaths = " + mOverlayPaths + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@android.annotation.Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(OverlayPaths other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + OverlayPaths that = (OverlayPaths) o; + //noinspection PointlessBooleanExpression + return true + && Objects.equals(mResourceDirs, that.mResourceDirs) + && Objects.equals(mOverlayPaths, that.mOverlayPaths); + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + Objects.hashCode(mResourceDirs); + _hash = 31 * _hash + Objects.hashCode(mOverlayPaths); + return _hash; + } + + @DataClass.Generated( + time = 1612307813586L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/core/java/android/content/pm/overlay/OverlayPaths.java", + inputSignatures = "private final @android.annotation.NonNull java.util.List<java.lang.String> mResourceDirs\nprivate final @android.annotation.NonNull java.util.List<java.lang.String> mOverlayPaths\npublic boolean isEmpty()\nclass OverlayPaths extends java.lang.Object implements []\nfinal android.content.pm.overlay.OverlayPaths mPaths\npublic android.content.pm.overlay.OverlayPaths.Builder addNonApkPath(java.lang.String)\npublic android.content.pm.overlay.OverlayPaths.Builder addApkPath(java.lang.String)\npublic android.content.pm.overlay.OverlayPaths.Builder addAll(android.content.pm.overlay.OverlayPaths)\npublic android.content.pm.overlay.OverlayPaths build()\nprivate static void addUniquePath(java.util.List<java.lang.String>,java.lang.String)\nclass Builder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=false, genHiddenBuilder=false, genEqualsHashCode=true, genToString=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java index b7365b3eaf61..fb0d90490567 100644 --- a/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java +++ b/core/java/android/content/pm/parsing/PackageInfoWithoutStateUtils.java @@ -41,6 +41,7 @@ import android.content.pm.SELinuxUtil; import android.content.pm.ServiceInfo; import android.content.pm.Signature; import android.content.pm.SigningInfo; +import android.content.pm.overlay.OverlayPaths; import android.content.pm.parsing.component.ComponentParseUtils; import android.content.pm.parsing.component.ParsedActivity; import android.content.pm.parsing.component.ParsedAttribution; @@ -412,7 +413,11 @@ public class PackageInfoWithoutStateUtils { ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName); } ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state); - ai.resourceDirs = state.getAllOverlayPaths(); + final OverlayPaths overlayPaths = state.getAllOverlayPaths(); + if (overlayPaths != null) { + ai.resourceDirs = overlayPaths.getResourceDirs().toArray(new String[0]); + ai.overlayPaths = overlayPaths.getOverlayPaths().toArray(new String[0]); + } return ai; } diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java index 05769ddc5397..99b56a82173e 100644 --- a/core/java/android/content/res/ResourcesKey.java +++ b/core/java/android/content/res/ResourcesKey.java @@ -38,7 +38,7 @@ public final class ResourcesKey { public final String[] mSplitResDirs; @Nullable - public final String[] mOverlayDirs; + public final String[] mOverlayPaths; @Nullable public final String[] mLibDirs; @@ -67,7 +67,7 @@ public final class ResourcesKey { public ResourcesKey(@Nullable String resDir, @Nullable String[] splitResDirs, - @Nullable String[] overlayDirs, + @Nullable String[] overlayPaths, @Nullable String[] libDirs, int overrideDisplayId, @Nullable Configuration overrideConfig, @@ -75,7 +75,7 @@ public final class ResourcesKey { @Nullable ResourcesLoader[] loader) { mResDir = resDir; mSplitResDirs = splitResDirs; - mOverlayDirs = overlayDirs; + mOverlayPaths = overlayPaths; mLibDirs = libDirs; mLoaders = (loader != null && loader.length == 0) ? null : loader; mDisplayId = overrideDisplayId; @@ -86,7 +86,7 @@ public final class ResourcesKey { int hash = 17; hash = 31 * hash + Objects.hashCode(mResDir); hash = 31 * hash + Arrays.hashCode(mSplitResDirs); - hash = 31 * hash + Arrays.hashCode(mOverlayDirs); + hash = 31 * hash + Arrays.hashCode(mOverlayPaths); hash = 31 * hash + Arrays.hashCode(mLibDirs); hash = 31 * hash + Objects.hashCode(mDisplayId); hash = 31 * hash + Objects.hashCode(mOverrideConfiguration); @@ -98,12 +98,12 @@ public final class ResourcesKey { @UnsupportedAppUsage public ResourcesKey(@Nullable String resDir, @Nullable String[] splitResDirs, - @Nullable String[] overlayDirs, + @Nullable String[] overlayPaths, @Nullable String[] libDirs, int displayId, @Nullable Configuration overrideConfig, @Nullable CompatibilityInfo compatInfo) { - this(resDir, splitResDirs, overlayDirs, libDirs, displayId, overrideConfig, compatInfo, + this(resDir, splitResDirs, overlayPaths, libDirs, displayId, overrideConfig, compatInfo, null); } @@ -115,7 +115,7 @@ public final class ResourcesKey { if (mResDir != null && mResDir.startsWith(path)) { return true; } else { - return anyStartsWith(mSplitResDirs, path) || anyStartsWith(mOverlayDirs, path) + return anyStartsWith(mSplitResDirs, path) || anyStartsWith(mOverlayPaths, path) || anyStartsWith(mLibDirs, path); } } @@ -154,7 +154,7 @@ public final class ResourcesKey { if (!Arrays.equals(mSplitResDirs, peer.mSplitResDirs)) { return false; } - if (!Arrays.equals(mOverlayDirs, peer.mOverlayDirs)) { + if (!Arrays.equals(mOverlayPaths, peer.mOverlayPaths)) { return false; } if (!Arrays.equals(mLibDirs, peer.mLibDirs)) { @@ -186,8 +186,8 @@ public final class ResourcesKey { } builder.append("]"); builder.append(" mOverlayDirs=["); - if (mOverlayDirs != null) { - builder.append(TextUtils.join(",", mOverlayDirs)); + if (mOverlayPaths != null) { + builder.append(TextUtils.join(",", mOverlayPaths)); } builder.append("]"); builder.append(" mLibDirs=["); diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 39e3e146f45b..8068c872c4bb 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -1442,7 +1442,7 @@ public class Process { * * @hide */ - public static boolean hasFileLocks(int pid) throws IOException { + public static boolean hasFileLocks(int pid) throws Exception { BufferedReader br = null; try { @@ -1454,8 +1454,13 @@ public class Process { for (int i = 0; i < 5 && st.hasMoreTokens(); i++) { String str = st.nextToken(); - if (i == 4 && Integer.parseInt(str) == pid) { - return true; + try { + if (i == 4 && Integer.parseInt(str) == pid) { + return true; + } + } catch (NumberFormatException nfe) { + throw new Exception("Exception parsing /proc/locks at \" " + + line + " \", token #" + i); } } } diff --git a/core/java/android/provider/FontsContract.java b/core/java/android/provider/FontsContract.java index faa90d93719f..86b20c424869 100644 --- a/core/java/android/provider/FontsContract.java +++ b/core/java/android/provider/FontsContract.java @@ -350,6 +350,9 @@ public class FontsContract { return cachedTypeface; } + Log.w(TAG, "Platform version of downloadable fonts is deprecated. Please use" + + " androidx version instead."); + synchronized (sLock) { // It is possible that Font is loaded during the thread sleep time // re-check the cache to avoid re-loading the font diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 990b7bdfa987..dad932c2ee19 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -123,14 +123,6 @@ interface IWindowSession { boolean outOfMemory(IWindow window); /** - * Give the window manager a hint of the part of the window that is - * completely transparent, allowing it to work with the surface flinger - * to optimize compositing of this part of the window. - */ - @UnsupportedAppUsage - oneway void setTransparentRegion(IWindow window, in Region region); - - /** * Tell the window manager about the content and visible insets of the * given window, which can be used to adjust the <var>outContentInsets</var> * and <var>outVisibleInsets</var> values returned by diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index e1ccc51c71e1..1273b491d0e1 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -740,6 +740,7 @@ import java.util.function.Predicate; * @attr ref android.R.styleable#View_alpha * @attr ref android.R.styleable#View_background * @attr ref android.R.styleable#View_clickable + * @attr ref android.R.styleable#View_clipToOutline * @attr ref android.R.styleable#View_contentDescription * @attr ref android.R.styleable#View_drawingCacheQuality * @attr ref android.R.styleable#View_duplicateParentState @@ -5968,6 +5969,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, case R.styleable.View_scrollCaptureHint: setScrollCaptureHint((a.getInt(attr, SCROLL_CAPTURE_HINT_AUTO))); break; + case R.styleable.View_clipToOutline: + setClipToOutline(a.getBoolean(attr, false)); + break; } } @@ -17921,6 +17925,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @see #setOutlineProvider(ViewOutlineProvider) * @see #getClipToOutline() + * + * @attr ref android.R.styleable#View_clipToOutline */ @RemotableViewMethod public void setClipToOutline(boolean clipToOutline) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 56d98e78303e..4716141ee8d3 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1675,7 +1675,8 @@ public final class ViewRootImpl implements ViewParent, requestLayout(); // See comment for View.sForceLayoutWhenInsetsChanged - if (View.sForceLayoutWhenInsetsChanged && mView != null) { + if (View.sForceLayoutWhenInsetsChanged && mView != null + && mWindowAttributes.softInputMode == SOFT_INPUT_ADJUST_RESIZE) { forceLayout(mView); } @@ -3063,11 +3064,14 @@ public final class ViewRootImpl implements ViewParent, if (!mTransparentRegion.equals(mPreviousTransparentRegion)) { mPreviousTransparentRegion.set(mTransparentRegion); mFullRedrawNeeded = true; - // reconfigure window manager - try { - mWindowSession.setTransparentRegion(mWindow, mTransparentRegion); - } catch (RemoteException e) { - } + // TODO: Ideally we would do this in prepareSurfaces, + // but prepareSurfaces is currently working under + // the assumption that we paused the render thread + // via the WM relayout code path. We probably eventually + // want to synchronize transparent region hint changes + // with draws. + mTransaction.setTransparentRegionHint(getSurfaceControl(), + mTransparentRegion).apply(); } } diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index 3aedda1a6bd3..39d3c01dd409 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -314,10 +314,6 @@ public class WindowlessWindowManager implements IWindowSession { } @Override - public void setTransparentRegion(android.view.IWindow window, android.graphics.Region region) { - } - - @Override public void setInsets(android.view.IWindow window, int touchableInsets, android.graphics.Rect contentInsets, android.graphics.Rect visibleInsets, android.graphics.Region touchableRegion) { diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 794181e388cf..decbf8c0c59e 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -200,6 +200,34 @@ public final class AutofillManager { "android.view.autofill.extra.AUTHENTICATION_RESULT"; /** + * Intent extra: The optional boolean extra field provided by the + * {@link android.service.autofill.AutofillService} accompanying the {@link + * android.service.autofill.Dataset} result of an authentication operation. + * + * <p> Before {@link android.os.Build.VERSION_CODES#R}, if the authentication result is a + * {@link android.service.autofill.Dataset}, it'll be used to autofill the fields, and also + * replace the existing dataset in the cached {@link android.service.autofill.FillResponse}. + * That means if the user clears the field values, the autofill suggestion will show up again + * with the new authenticated Dataset. + * + * <p> In {@link android.os.Build.VERSION_CODES#R}, we added an exception to this behavior + * that if the Dataset being authenticated is a pinned dataset (see + * {@link android.service.autofill.InlinePresentation#isPinned()}), the old Dataset will not be + * replaced. + * + * <p> In {@link android.os.Build.VERSION_CODES#S}, we added this boolean extra field to + * allow the {@link android.service.autofill.AutofillService} to explicitly specify whether + * the returned authenticated Dataset is ephemeral. An ephemeral Dataset will be used to + * autofill once and then thrown away. Therefore, when the boolean extra is set to true, the + * returned Dataset will not replace the old dataset from the existing + * {@link android.service.autofill.FillResponse}. When it's set to false, it will. When it's not + * set, the old dataset will be replaced, unless it is a pinned inline suggestion, which is + * consistent with the behavior in {@link android.os.Build.VERSION_CODES#R}. + */ + public static final String EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET = + "android.view.autofill.extra.AUTHENTICATION_RESULT_EPHEMERAL_DATASET"; + + /** * Intent extra: The optional extras provided by the * {@link android.service.autofill.AutofillService}. * @@ -1755,6 +1783,11 @@ public final class AutofillManager { if (newClientState != null) { responseData.putBundle(EXTRA_CLIENT_STATE, newClientState); } + if (data.getExtras().containsKey(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) { + responseData.putBoolean(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET, + data.getBooleanExtra(EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET, + false)); + } try { mService.setAuthenticationResult(responseData, mSessionId, authenticationId, mContext.getUserId()); diff --git a/core/proto/android/content/package_item_info.proto b/core/proto/android/content/package_item_info.proto index bb39ea810add..5c6116a09b77 100644 --- a/core/proto/android/content/package_item_info.proto +++ b/core/proto/android/content/package_item_info.proto @@ -114,4 +114,5 @@ message ApplicationInfoProto { optional bool native_heap_zero_init = 21; } optional Detail detail = 17; + repeated string overlay_paths = 18; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 827bf7b70cbc..856657ad8b56 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -2698,11 +2698,11 @@ The app can check whether it has this authorization by calling {@link android.provider.Settings#canDrawOverlays Settings.canDrawOverlays()}. - <p>Protection level: signature|appop|installer|recents|appPredictor|pre23|development --> + <p>Protection level: signature|appop|installer|appPredictor|pre23|development --> <permission android:name="android.permission.SYSTEM_ALERT_WINDOW" android:label="@string/permlab_systemAlertWindow" android:description="@string/permdesc_systemAlertWindow" - android:protectionLevel="signature|appop|installer|recents|appPredictor|pre23|development" /> + android:protectionLevel="signature|appop|installer|appPredictor|pre23|development" /> <!-- @SystemApi @hide Allows an application to create windows using the type {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY}, @@ -5447,6 +5447,11 @@ <permission android:name="android.permission.MANAGE_TOAST_RATE_LIMITING" android:protectionLevel="signature" /> + <!-- Allows managing the Game Mode + @hide Used internally. --> + <permission android:name="android.permission.MANAGE_GAME_MODE" + android:protectionLevel="signature" /> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index e567c3d7d486..586c99d76335 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3251,6 +3251,16 @@ a value of 'true' will not override any 'false' value in its parent chain nor will it prevent any 'false' in any of its children. --> <attr name="forceDarkAllowed" format="boolean" /> + + <!-- <p>Whether the View's Outline should be used to clip the contents of the View. + <p>Only a single non-rectangular clip can be applied on a View at any time. Circular + clips from a + {@link android.view.ViewAnimationUtils#createCircularReveal(View, int, int, float, + float)} circular reveal animation take priority over Outline clipping, and child + Outline clipping takes priority over Outline clipping done by a parent. + <p>Note that this flag will only be respected if the View's Outline returns true from + {@link android.graphics.Outline#canClip()}. --> + <attr name="clipToOutline" format="boolean" /> </declare-styleable> <!-- Attributes that can be assigned to a tag for a particular View. --> diff --git a/core/res/res/values/colors_device_defaults.xml b/core/res/res/values/colors_device_defaults.xml index c8cccc45f634..f7234269f227 100644 --- a/core/res/res/values/colors_device_defaults.xml +++ b/core/res/res/values/colors_device_defaults.xml @@ -48,6 +48,8 @@ <color name="text_color_secondary_device_default_dark">@color/system_main_200</color> <color name="text_color_tertiary_device_default_light">@color/system_main_500</color> <color name="text_color_tertiary_device_default_dark">@color/system_main_400</color> + <color name="foreground_device_default_light">@color/text_color_primary_device_default_light</color> + <color name="foreground_device_default_dark">@color/text_color_primary_device_default_dark</color> <!-- Error color --> <color name="error_color_device_default_dark">@color/error_color_material_dark</color> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 8c5f454d204d..beae9353a10f 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4712,4 +4712,10 @@ <!-- Whether to select voice/data/sms preference without user confirmation --> <bool name="config_voice_data_sms_auto_fallback">false</bool> + + <!-- Whether to enable the one-handed keyguard on the lock screen for wide-screen devices. --> + <bool name="config_enableOneHandedKeyguard">false</bool> + + <!-- Whether to allow the caching of the SIM PIN for verification after unattended reboot --> + <bool name="config_allow_pin_storage_for_unattended_reboot">true</bool> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 97ec0f4fa71e..d3f3ebd7c3d9 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -3062,6 +3062,7 @@ <!-- @hide @SystemApi --> <public name="hotwordDetectionService" /> <public name="previewLayout" /> + <public name="clipToOutline" /> </public-group> <public-group type="drawable" first-id="0x010800b5"> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index c41b78e4a680..41375a7dd419 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2647,6 +2647,7 @@ <java-symbol type="bool" name="config_defaultWindowFeatureContextMenu" /> <java-symbol type="bool" name="config_overrideRemoteViewsActivityTransition" /> <java-symbol type="attr" name="colorProgressBackgroundNormal" /> + <java-symbol type="bool" name="config_allow_pin_storage_for_unattended_reboot" /> <java-symbol type="layout" name="simple_account_item" /> <java-symbol type="string" name="prohibit_manual_network_selection_in_gobal_mode" /> diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml index ee17f6f7ce21..ce4ee87e52a0 100644 --- a/core/res/res/values/themes_device_defaults.xml +++ b/core/res/res/values/themes_device_defaults.xml @@ -222,6 +222,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> </style> <style name="Theme.DeviceDefault" parent="Theme.DeviceDefaultBase" /> @@ -236,6 +238,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -267,6 +271,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -300,6 +306,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -332,6 +340,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -381,6 +391,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -405,6 +417,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -435,6 +449,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -466,6 +482,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -513,6 +531,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -545,6 +565,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -575,6 +597,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -607,6 +631,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -638,6 +664,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -669,6 +697,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -700,6 +730,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -731,6 +763,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -766,6 +800,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Text styles --> <item name="textAppearanceButton">@style/TextAppearance.DeviceDefault.Widget.Button</item> @@ -798,6 +834,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -827,6 +865,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1012,6 +1052,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <item name="colorPopupBackground">?attr/colorBackgroundFloating</item> <item name="panelColorBackground">?attr/colorBackgroundFloating</item> </style> @@ -1027,6 +1069,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1057,6 +1101,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1088,6 +1134,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1121,6 +1169,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1153,6 +1203,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1204,6 +1256,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Progress bar attributes --> <item name="colorProgressBackgroundNormal">@color/config_progress_background_tint</item> @@ -1227,6 +1281,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1260,6 +1316,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1294,6 +1352,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1329,6 +1389,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> </style> <!-- Variant of Theme.DeviceDefault.Dialog.NoActionBar that has a fixed size. --> @@ -1346,6 +1408,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> </style> <!-- DeviceDefault light theme for a window that will be displayed either full-screen on smaller @@ -1362,6 +1426,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1397,6 +1463,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1430,6 +1498,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1462,6 +1532,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1493,6 +1565,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1524,6 +1598,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1553,6 +1629,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1672,6 +1750,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item> + <item name="colorForeground">@color/foreground_device_default_dark</item> + <item name="colorForegroundInverse">@color/foreground_device_default_light</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1702,6 +1782,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item> @@ -1742,6 +1824,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> @@ -1775,6 +1859,8 @@ easier. <item name="textColorPrimary">@color/text_color_primary_device_default_light</item> <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item> <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item> + <item name="colorForeground">@color/foreground_device_default_light</item> + <item name="colorForegroundInverse">@color/foreground_device_default_dark</item> <!-- Dialog attributes --> <item name="dialogCornerRadius">@dimen/config_dialogCornerRadius</item> diff --git a/core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java b/core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java index cfcfcc8cf044..ece37f8054cf 100644 --- a/core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java +++ b/core/tests/coretests/src/android/app/appsearch/external/util/BundleUtilTest.java @@ -201,6 +201,18 @@ public class BundleUtilTest { assertThat(BundleUtil.deepHashCode(b1)).isNotEqualTo(BundleUtil.deepHashCode(b2)); } + @Test + public void testDeepHashCode_differentKeys() { + Bundle[] inputs = new Bundle[2]; + for (int i = 0; i < 2; i++) { + Bundle b = new Bundle(); + b.putString("key" + i, "value"); + inputs[i] = b; + } + assertThat(BundleUtil.deepHashCode(inputs[0])) + .isNotEqualTo(BundleUtil.deepHashCode(inputs[1])); + } + private static Bundle createThoroughBundle() { Bundle toy1 = new Bundle(); toy1.putString("a", "a"); diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java index 45adf833de97..46dbe0fd658a 100644 --- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java +++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java @@ -90,12 +90,12 @@ public class ResourcesManagerTest extends TestCase { @SmallTest public void testMultipleCallsWithIdenticalParametersCacheReference() { Resources resources = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, null, null, null, null, null, + null, APP_ONE_RES_DIR, null, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources); Resources newResources = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, null, null, null, null, null, + null, APP_ONE_RES_DIR, null, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(newResources); assertSame(resources, newResources); @@ -104,14 +104,14 @@ public class ResourcesManagerTest extends TestCase { @SmallTest public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() { Resources resources = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, null, null, null, null, null, + null, APP_ONE_RES_DIR, null, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources); Configuration overrideConfig = new Configuration(); overrideConfig.smallestScreenWidthDp = 200; Resources newResources = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, null, null, null, null, overrideConfig, + null, APP_ONE_RES_DIR, null, null, null, null, null, overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(newResources); assertNotSame(resources, newResources); @@ -120,12 +120,12 @@ public class ResourcesManagerTest extends TestCase { @SmallTest public void testAddingASplitCreatesANewImpl() { Resources resources1 = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, null, null, null, null, null, + null, APP_ONE_RES_DIR, null, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Resources resources2 = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, new String[] { APP_ONE_RES_SPLIT_DIR }, null, null, + null, APP_ONE_RES_DIR, new String[] { APP_ONE_RES_SPLIT_DIR }, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources2); @@ -137,12 +137,12 @@ public class ResourcesManagerTest extends TestCase { @SmallTest public void testUpdateConfigurationUpdatesAllAssetManagers() { Resources resources1 = mResourcesManager.getResources( - null, APP_ONE_RES_DIR, null, null, null, null, null, + null, APP_ONE_RES_DIR, null, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Resources resources2 = mResourcesManager.getResources( - null, APP_TWO_RES_DIR, null, null, null, null, null, + null, APP_TWO_RES_DIR, null, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources2); @@ -150,7 +150,7 @@ public class ResourcesManagerTest extends TestCase { final Configuration overrideConfig = new Configuration(); overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE; Resources resources3 = mResourcesManager.getResources( - activity, APP_ONE_RES_DIR, null, null, null, null, + activity, APP_ONE_RES_DIR, null, null, null, null, null, overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources3); @@ -183,13 +183,13 @@ public class ResourcesManagerTest extends TestCase { public void testTwoActivitiesWithIdenticalParametersShareImpl() { Binder activity1 = new Binder(); Resources resources1 = mResourcesManager.getResources( - activity1, APP_ONE_RES_DIR, null, null, null, null, null, + activity1, APP_ONE_RES_DIR, null, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Binder activity2 = new Binder(); Resources resources2 = mResourcesManager.getResources( - activity2, APP_ONE_RES_DIR, null, null, null, null, null, + activity2, APP_ONE_RES_DIR, null, null, null, null, null, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); @@ -204,7 +204,7 @@ public class ResourcesManagerTest extends TestCase { public void testThemesGetUpdatedWithNewImpl() { Binder activity1 = new Binder(); Resources resources1 = mResourcesManager.createBaseTokenResources( - activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + activity1, APP_ONE_RES_DIR, null, null, null, null, Display.DEFAULT_DISPLAY, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); @@ -237,15 +237,15 @@ public class ResourcesManagerTest extends TestCase { Configuration config1 = new Configuration(); config1.densityDpi = 280; Resources resources1 = mResourcesManager.createBaseTokenResources( - activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, config1, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); + activity1, APP_ONE_RES_DIR, null, null, null, null, Display.DEFAULT_DISPLAY, + config1, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); // Create a Resources based on the Activity. Configuration config2 = new Configuration(); config2.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES; Resources resources2 = mResourcesManager.getResources( - activity1, APP_ONE_RES_DIR, null, null, null, null, config2, + activity1, APP_ONE_RES_DIR, null, null, null, null, null, config2, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources2); @@ -286,8 +286,8 @@ public class ResourcesManagerTest extends TestCase { final Configuration overrideConfig = new Configuration(); overrideConfig.densityDpi = originalOverrideDensity; final Resources resources = mResourcesManager.createBaseTokenResources( - token, APP_ONE_RES_DIR, null /* splitResDirs */, null /* overlayDirs */, - null /* libDirs */, Display.DEFAULT_DISPLAY, overrideConfig, + token, APP_ONE_RES_DIR, null /* splitResDirs */, null /* legacyOverlayDirs */, + null /* overlayDirs */,null /* libDirs */, Display.DEFAULT_DISPLAY, overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null /* classLoader */, null /* loaders */); @@ -315,12 +315,12 @@ public class ResourcesManagerTest extends TestCase { // Create a base token resources that are based on the default display. Resources activityResources = mResourcesManager.createBaseTokenResources( - activity, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + activity, APP_ONE_RES_DIR, null, null, null,null, Display.DEFAULT_DISPLAY, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); // Create another resources that explicitly override the display of the base token above // and set it to DEFAULT_DISPLAY. Resources defaultDisplayResources = mResourcesManager.getResources( - activity, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, + activity, APP_ONE_RES_DIR, null, null, null, null, Display.DEFAULT_DISPLAY, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertEquals(mDisplayMetricsMap.get(Display.DEFAULT_DISPLAY).widthPixels, diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java index 64906bb27ff0..e16d44854516 100644 --- a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java @@ -100,7 +100,7 @@ public class ResolverListControllerTest { final List<UsageStats> slices = new ArrayList<>(); slices.add(packageStats); ParceledListSlice<UsageStats> stats = new ParceledListSlice<>(slices); - when(mMockService.queryUsageStats(anyInt(), anyLong(), anyLong(), anyString())) + when(mMockService.queryUsageStats(anyInt(), anyLong(), anyLong(), anyString(), anyInt())) .thenReturn(stats); Answer<Void> answer = new Answer<Void>() { @Override diff --git a/graphics/java/android/graphics/HardwareRenderer.java b/graphics/java/android/graphics/HardwareRenderer.java index cb4dd9e8cacd..b70fa0e693c2 100644 --- a/graphics/java/android/graphics/HardwareRenderer.java +++ b/graphics/java/android/graphics/HardwareRenderer.java @@ -46,7 +46,6 @@ import java.io.File; import java.io.FileDescriptor; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Arrays; import java.util.Optional; import java.util.concurrent.Executor; import java.util.stream.Stream; @@ -1148,24 +1147,14 @@ public class HardwareRenderer { // Default to SRGB if the display doesn't support wide color .orElse(Dataspace.SRGB); - float maxRefreshRate = - (float) Arrays.stream(display.getSupportedModes()) - .mapToDouble(Mode::getRefreshRate) - .max() - .orElseGet(() -> { - Log.i(LOG_TAG, "Failed to find the maximum display refresh rate"); - // Assume that the max refresh rate is 60hz if we can't find one. - return 60.0; - }); // Grab the physical screen dimensions from the active display mode // Strictly speaking the screen resolution may not always be constant - it is for // sizing the font cache for the underlying rendering thread. Since it's a // heuristic we don't need to be always 100% correct. Mode activeMode = display.getMode(); nInitDisplayInfo(activeMode.getPhysicalWidth(), activeMode.getPhysicalHeight(), - display.getRefreshRate(), maxRefreshRate, - wideColorDataspace.mNativeDataspace, display.getAppVsyncOffsetNanos(), - display.getPresentationDeadlineNanos()); + display.getRefreshRate(), wideColorDataspace.mNativeDataspace, + display.getAppVsyncOffsetNanos(), display.getPresentationDeadlineNanos()); // Defensively clear out the context mContext = null; @@ -1324,6 +1313,5 @@ public class HardwareRenderer { private static native void nSetDisplayDensityDpi(int densityDpi); private static native void nInitDisplayInfo(int width, int height, float refreshRate, - float maxRefreshRate, int wideColorDataspace, long appVsyncOffsetNanos, - long presentationDeadlineNanos); + int wideColorDataspace, long appVsyncOffsetNanos, long presentationDeadlineNanos); } diff --git a/keystore/java/android/security/KeyStoreSecurityLevel.java b/keystore/java/android/security/KeyStoreSecurityLevel.java index 372add9b7ecb..d188b6525579 100644 --- a/keystore/java/android/security/KeyStoreSecurityLevel.java +++ b/keystore/java/android/security/KeyStoreSecurityLevel.java @@ -190,7 +190,7 @@ public class KeyStoreSecurityLevel { keyDescriptor.blob = wrappedKey; keyDescriptor.domain = wrappedKeyDescriptor.domain; - return handleExceptions(() -> mSecurityLevel.importWrappedKey(wrappedKeyDescriptor, + return handleExceptions(() -> mSecurityLevel.importWrappedKey(keyDescriptor, wrappingKeyDescriptor, maskingKey, args.toArray(new KeyParameter[args.size()]), authenticatorSpecs)); } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java index 8475ad9fd57b..0f777495a3fe 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreHmacSpi.java @@ -164,6 +164,9 @@ public abstract class AndroidKeyStoreHmacSpi extends MacSpi implements KeyStoreC List<KeyParameter> parameters = new ArrayList<>(); parameters.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_PURPOSE, KeymasterDefs.KM_PURPOSE_SIGN + )); + parameters.add(KeyStore2ParameterUtils.makeEnum( KeymasterDefs.KM_TAG_ALGORITHM, KeymasterDefs.KM_ALGORITHM_HMAC )); parameters.add(KeyStore2ParameterUtils.makeEnum( diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java index 32650aeda1b1..5619585d9c3c 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKey.java @@ -21,7 +21,6 @@ import android.security.KeyStoreSecurityLevel; import android.system.keystore2.Authorization; import android.system.keystore2.Domain; import android.system.keystore2.KeyDescriptor; -import android.util.Log; import java.security.Key; @@ -127,15 +126,6 @@ public class AndroidKeyStoreKey implements Key { return false; } - // If the key ids are equal and the class matches all the other fields cannot differ - // unless we have a bug. - if (!mAlgorithm.equals(other.mAlgorithm) - || !mAuthorizations.equals(other.mAuthorizations) - || !mDescriptor.equals(other.mDescriptor)) { - Log.e("AndroidKeyStoreKey", "Bug: key ids are identical, but key metadata" - + "differs."); - return false; - } return true; } } diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index 8c8acc418a0e..39607aeb3852 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -866,7 +866,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { try { response = mKeyStore.getKeyEntry(wrappingkey); } catch (android.security.KeyStoreException e) { - throw new KeyStoreException("Failed to load wrapping key.", e); + throw new KeyStoreException("Failed to import wrapped key. Keystore error code: " + + e.getErrorCode(), e); } KeyDescriptor wrappedKey = makeKeyDescriptor(alias); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java index 177646b22ea3..7ca569349633 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java @@ -106,6 +106,8 @@ public interface SplitScreen extends DragAndDropPolicy.Starter { /** Removes the split-screen stages. */ void exitSplitScreen(); + /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */ + void exitSplitScreenOnHide(boolean exitSplitScreenOnHide); /** Gets the stage bounds. */ void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds); 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 bbad36dcc046..b0167afa2e4e 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 @@ -126,6 +126,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter { mStageCoordinator.exitSplitScreen(); } + public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { + mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide); + } + public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) { mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds); } @@ -292,6 +296,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter { } @Override + public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { + mMainExecutor.execute(() -> { + SplitScreenController.this.exitSplitScreenOnHide(exitSplitScreenOnHide); + }); + } + + @Override public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) { try { mMainExecutor.executeBlocking(() -> { 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 176852b148fa..2d4b77e0c630 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 @@ -79,6 +79,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, private DisplayAreaInfo mDisplayAreaInfo; private final Context mContext; private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>(); + private boolean mExitSplitScreenOnHide = true; StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue, RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer) { @@ -113,7 +114,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, boolean moveToSideStage(ActivityManager.RunningTaskInfo task, @SplitScreen.StagePosition int sideStagePosition) { final WindowContainerTransaction wct = new WindowContainerTransaction(); - mSideStagePosition = sideStagePosition; + setSideStagePosition(sideStagePosition); mMainStage.activate(getMainStageBounds(), wct); mSideStage.addTask(task, getSideStageBounds(), wct); mTaskOrganizer.applyTransaction(wct); @@ -144,10 +145,14 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, } void setSideStagePosition(@SplitScreen.StagePosition int sideStagePosition) { + if (mSideStagePosition == sideStagePosition) return; + mSideStagePosition = sideStagePosition; if (mSideStageListener.mVisible) { onStageVisibilityChanged(mSideStageListener); } + + sendOnStagePositionChanged(); } void setSideStageVisibility(boolean visible) { @@ -162,6 +167,10 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, exitSplitScreen(null /* childrenToTop */); } + void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { + mExitSplitScreenOnHide = exitSplitScreenOnHide; + } + private void exitSplitScreen(StageTaskListener childrenToTop) { final WindowContainerTransaction wct = new WindowContainerTransaction(); mSideStage.removeAllTasks(wct, childrenToTop == mSideStage); @@ -202,6 +211,14 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, mListeners.remove(listener); } + private void sendOnStagePositionChanged() { + for (int i = mListeners.size() - 1; i >= 0; --i) { + final SplitScreen.SplitScreenListener l = mListeners.get(i); + l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition()); + l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition()); + } + } + private void onStageChildTaskStatusChanged( StageListenerImpl stageListener, int taskId, boolean present) { @@ -251,7 +268,7 @@ class StageCoordinator implements SplitLayout.LayoutChangeListener, } } - if (!mainStageVisible && !sideStageVisible) { + if (mExitSplitScreenOnHide && !mainStageVisible && !sideStageVisible) { // Exit split-screen if both stage are not visible. // TODO: This is only a temporary request from UX and is likely to be removed soon... exitSplitScreen(); diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt new file mode 100644 index 000000000000..1869d833fbc4 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/Extensions.kt @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 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:JvmName("Utils") +package com.android.wm.shell.flicker + +import android.app.ActivityTaskManager +import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT +import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS +import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD +import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED + +fun removeAllTasksButHome() { + val ALL_ACTIVITY_TYPE_BUT_HOME = intArrayOf( + ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS, + ACTIVITY_TYPE_UNDEFINED) + val atm = ActivityTaskManager.getService() + atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME) +} diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt index 7ec22bb9db1c..cac46fe676b3 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/ImeAppHelper.kt @@ -32,13 +32,12 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper( /** * Opens the IME and wait for it to be displayed * - * @param device UIDevice instance to interact with the device * @param wmHelper Helper used to wait for WindowManager states */ @JvmOverloads - open fun openIME(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) { + open fun openIME(wmHelper: WindowManagerStateHelper? = null) { if (!isTelevision) { - val editText = device.wait( + val editText = uiDevice.wait( Until.findObject(By.res(getPackage(), "plain_text_input")), FIND_TIMEOUT) @@ -47,7 +46,7 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper( "was left in an unknown state (e.g. in split screen)" } editText.click() - waitAndAssertIMEShown(device, wmHelper) + waitAndAssertIMEShown(uiDevice, wmHelper) } else { // If we do the same thing as above - editText.click() - on TV, that's going to force TV // into the touch mode. We really don't want that. @@ -69,16 +68,15 @@ open class ImeAppHelper(instrumentation: Instrumentation) : BaseAppHelper( /** * Opens the IME and wait for it to be gone * - * @param device UIDevice instance to interact with the device * @param wmHelper Helper used to wait for WindowManager states */ @JvmOverloads - open fun closeIME(device: UiDevice, wmHelper: WindowManagerStateHelper? = null) { + open fun closeIME(wmHelper: WindowManagerStateHelper? = null) { if (!isTelevision) { - device.pressBack() + uiDevice.pressBack() // Using only the AccessibilityInfo it is not possible to identify if the IME is active if (wmHelper == null) { - device.waitForIdle() + uiDevice.waitForIdle() } else { require(wmHelper.waitImeWindowGone()) { "IME did did not close" } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt index b90e865de691..111362a93495 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt @@ -17,17 +17,20 @@ package com.android.wm.shell.flicker.helpers import android.app.Instrumentation +import android.graphics.Point import android.media.session.MediaController import android.media.session.MediaSessionManager import android.os.SystemClock import androidx.test.uiautomator.By import androidx.test.uiautomator.BySelector +import com.android.server.wm.flicker.helpers.SYSTEMUI_PACKAGE import com.android.server.wm.flicker.helpers.closePipWindow -import com.android.server.wm.flicker.helpers.hasPipWindow +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper import com.android.wm.shell.flicker.pip.tv.closeTvPipWindow import com.android.wm.shell.flicker.pip.tv.isFocusedOrHasFocusedChild +import com.android.wm.shell.flicker.pip.waitPipWindowGone +import com.android.wm.shell.flicker.pip.waitPipWindowShown import com.android.wm.shell.flicker.testapp.Components -import org.junit.Assert.fail class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( instrumentation, @@ -55,6 +58,17 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( } } + /** {@inheritDoc} */ + override fun launchViaIntent( + wmHelper: WindowManagerStateHelper, + expectedWindowName: String, + action: String?, + stringExtras: Map<String, String> + ) { + super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras) + wmHelper.waitPipWindowShown() + } + private fun focusOnObject(selector: BySelector): Boolean { // We expect all the focusable UI elements to be arranged in a way so that it is possible // to "cycle" over all them by clicking the D-Pad DOWN button, going back up to "the top" @@ -69,16 +83,12 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( return false } - fun clickEnterPipButton() { + @JvmOverloads + fun clickEnterPipButton(wmHelper: WindowManagerStateHelper? = null) { clickObject(ENTER_PIP_BUTTON_ID) - // TODO(b/172321238): remove this check once hasPipWindow is fixed on TVs - if (!isTelevision) { - uiDevice.hasPipWindow() - } else { - // Simply wait for 3 seconds - SystemClock.sleep(3_000) - } + // Wait on WMHelper or simply wait for 3 seconds + wmHelper?.waitPipWindowShown() ?: SystemClock.sleep(3_000) } fun clickStartMediaSessionButton() { @@ -97,16 +107,75 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper( fun stopMedia() = mediaController?.transportControls?.stop() ?: error("No active media session found") + @Deprecated("Use PipAppHelper.closePipWindow(wmHelper) instead", + ReplaceWith("closePipWindow(wmHelper)")) fun closePipWindow() { if (isTelevision) { uiDevice.closeTvPipWindow() } else { uiDevice.closePipWindow() } + } + + /** + * Expands the pip window and dismisses it by clicking on the X button. + * + * Note, currently the View coordinates reported by the accessibility are relative to + * the window, so the correct coordinates need to be calculated + * + * For example, in a PIP window located at Rect(508, 1444 - 1036, 1741), the + * dismiss button coordinates are shown as Rect(650, 0 - 782, 132), with center in + * Point(716, 66), instead of Point(970, 1403) + * + * See b/179337864 + */ + fun closePipWindow(wmHelper: WindowManagerStateHelper) { + if (isTelevision) { + uiDevice.closeTvPipWindow() + } else { + expandPipWindow(wmHelper) + val exitPipObject = uiDevice.findObject(By.res(SYSTEMUI_PACKAGE, "dismiss")) + requireNotNull(exitPipObject) { "PIP window dismiss button not found" } + val coordinatesInWindow = exitPipObject.visibleBounds + val windowOffset = wmHelper.getWindowRegion(component).bounds + val newCoordinates = Point(windowOffset.left + coordinatesInWindow.centerX(), + windowOffset.top + coordinatesInWindow.centerY()) + uiDevice.click(newCoordinates.x, newCoordinates.y) + } + + // Wait for animation to complete. + wmHelper.waitPipWindowGone() + wmHelper.waitForHomeActivityVisible() + } + + /** + * Click once on the PIP window to expand it + */ + fun expandPipWindow(wmHelper: WindowManagerStateHelper) { + val windowRegion = wmHelper.getWindowRegion(component) + require(!windowRegion.isEmpty) { + "Unable to find a PIP window in the current state" + } + val windowRect = windowRegion.bounds + uiDevice.click(windowRect.centerX(), windowRect.centerY()) + // Ensure WindowManagerService wait until all animations have completed + wmHelper.waitForAppTransitionIdle() + mInstrumentation.uiAutomation.syncInputTransactions() + } - if (!waitUntilClosed()) { - fail("Couldn't close Pip") + /** + * Double click on the PIP window to reopen to app + */ + fun expandPipWindowToApp(wmHelper: WindowManagerStateHelper) { + val windowRegion = wmHelper.getWindowRegion(component) + require(!windowRegion.isEmpty) { + "Unable to find a PIP window in the current state" } + val windowRect = windowRegion.bounds + uiDevice.click(windowRect.centerX(), windowRect.centerY()) + uiDevice.click(windowRect.centerX(), windowRect.centerY()) + wmHelper.waitPipWindowGone() + wmHelper.waitForAppTransitionIdle() } companion object { diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt index 2015f4941cea..bc42d5ed04ce 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppTestBase.kt @@ -16,11 +16,6 @@ package com.android.wm.shell.flicker.pip -import android.app.ActivityTaskManager -import android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT -import android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS -import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD -import android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED import android.os.SystemClock import com.android.wm.shell.flicker.NonRotationTestBase @@ -29,14 +24,6 @@ abstract class AppTestBase( rotation: Int ) : NonRotationTestBase(rotationName, rotation) { companion object { - fun removeAllTasksButHome() { - val ALL_ACTIVITY_TYPE_BUT_HOME = intArrayOf( - ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS, - ACTIVITY_TYPE_UNDEFINED) - val atm = ActivityTaskManager.getService() - atm.removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME) - } - fun waitForAnimationComplete() { // TODO: UiDevice doesn't have reliable way to wait for the completion of animation. // Consider to introduce WindowManagerStateHelper to access Activity state. diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt index 5a3d18d9feef..a14b46ef7a3d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterExitPipTest.kt @@ -16,21 +16,21 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Presubmit +import android.os.Bundle import android.view.Surface import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.dsl.runFlicker +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerTestRunner +import com.android.server.wm.flicker.FlickerTestRunnerFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.wm.shell.flicker.helpers.FixedAppHelper -import com.android.wm.shell.flicker.helpers.PipAppHelper import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,82 +39,67 @@ import org.junit.runners.Parameterized * Test Pip launch and exit. * To run this test: `atest WMShellFlickerTests:EnterExitPipTest` */ -@Presubmit @RequiresDevice @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class EnterExitPipTest( - rotationName: String, - rotation: Int -) : AppTestBase(rotationName, rotation) { - private val pipApp = PipAppHelper(instrumentation) - private val testApp = FixedAppHelper(instrumentation) - - @Test - fun testDisplayMetricsPinUnpin() { - runFlicker(instrumentation) { - withTestName { "testDisplayMetricsPinUnpin" } - setup { - test { - removeAllTasksButHome() - device.wakeUpAndGoToHomeScreen() - pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true")) - testApp.launchViaIntent() - waitForAnimationComplete() - } - } - transitions { - // This will bring PipApp to fullscreen - pipApp.launchViaIntent() - waitForAnimationComplete() - } - teardown { - test { - removeAllTasksButHome() - } - } - assertions { - val displayBounds = WindowUtils.getDisplayBounds(rotation) - windowManagerTrace { - all("pipApp must remain inside visible bounds") { - coversAtMostRegion(pipApp.defaultWindowName, displayBounds) - } - all("Initially shows both app windows then pipApp hides testApp") { - showsAppWindow(testApp.defaultWindowName) - .showsAppWindowOnTop(pipApp.defaultWindowName) - .then() - .hidesAppWindow(testApp.defaultWindowName) + testSpec: FlickerTestRunnerFactory.TestSpec +) : FlickerTestRunner(testSpec) { + companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): List<Array<Any>> { + val testApp = FixedAppHelper(instrumentation) + val baseConfig = getTransitionLaunch(eachRun = true) + val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration -> + setup { + eachRun { + testApp.launchViaIntent(wmHelper) } - navBarWindowIsAlwaysVisible() - statusBarWindowIsAlwaysVisible() } - layersTrace { - all("Initially shows both app layers then pipApp hides testApp") { - showsLayer(testApp.defaultWindowName) - .showsLayer(pipApp.defaultWindowName) - .then() - .hidesLayer(testApp.defaultWindowName) - } - start("testApp covers the fullscreen, pipApp remains inside display") { - hasVisibleRegion(testApp.defaultWindowName, displayBounds) - coversAtMostRegion(displayBounds, pipApp.defaultWindowName) - } - end("pipApp covers the fullscreen") { - hasVisibleRegion(pipApp.defaultWindowName, displayBounds) + transitions { + // This will bring PipApp to fullscreen + pipApp.launchViaIntent(wmHelper) + } + assertions { + val displayBounds = WindowUtils.getDisplayBounds(configuration.startRotation) + presubmit { + windowManagerTrace { + all("pipApp must remain inside visible bounds") { + coversAtMostRegion(pipApp.defaultWindowName, displayBounds) + } + all("Initially shows both app windows then pipApp hides testApp") { + showsAppWindow(testApp.defaultWindowName) + .showsAppWindowOnTop(pipApp.defaultWindowName) + .then() + .hidesAppWindow(testApp.defaultWindowName) + } + navBarWindowIsAlwaysVisible() + statusBarWindowIsAlwaysVisible() + } + layersTrace { + all("Initially shows both app layers then pipApp hides testApp") { + showsLayer(testApp.defaultWindowName) + .showsLayer(pipApp.defaultWindowName) + .then() + .hidesLayer(testApp.defaultWindowName) + } + start("testApp covers the fullscreen, pipApp remains inside display") { + hasVisibleRegion(testApp.defaultWindowName, displayBounds) + coversAtMostRegion(displayBounds, pipApp.defaultWindowName) + } + end("pipApp covers the fullscreen") { + hasVisibleRegion(pipApp.defaultWindowName, displayBounds) + } + navBarLayerIsAlwaysVisible() + statusBarLayerIsAlwaysVisible() + } } - navBarLayerIsAlwaysVisible() - statusBarLayerIsAlwaysVisible() } } - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<Array<Any>> { - val supportedRotations = intArrayOf(Surface.ROTATION_0) - return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) } + return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig, + testSpec, supportedRotations = listOf(Surface.ROTATION_0), + repetitions = 5) } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt index af62eb9ae40d..99a40daa027f 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt @@ -16,18 +16,13 @@ package com.android.wm.shell.flicker.pip +import android.os.Bundle import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerTestRunner import com.android.server.wm.flicker.FlickerTestRunnerFactory -import com.android.server.wm.flicker.helpers.buildTestTag -import com.android.server.wm.flicker.helpers.closePipWindow -import com.android.server.wm.flicker.helpers.expandPipWindow -import com.android.server.wm.flicker.helpers.hasPipWindow -import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible @@ -35,9 +30,7 @@ import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation -import com.android.wm.shell.flicker.helpers.PipAppHelper import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -50,80 +43,58 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@FlakyTest(bugId = 152738416) class EnterPipTest( testSpec: FlickerTestRunnerFactory.TestSpec ) : FlickerTestRunner(testSpec) { - companion object { + companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) { @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): List<Array<Any>> { - val instrumentation = InstrumentationRegistry.getInstrumentation() - val testApp = PipAppHelper(instrumentation) - return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, - supportedRotations = listOf(Surface.ROTATION_0)) { configuration -> - withTestName { buildTestTag("enterPip", testApp, configuration) } - repeat { configuration.repetitions } - setup { - test { - device.wakeUpAndGoToHomeScreen() - } - eachRun { - device.pressHome() - testApp.launchViaIntent(wmHelper) - this.setRotation(configuration.startRotation) - } - } - teardown { - eachRun { - if (device.hasPipWindow()) { - device.closePipWindow() - } - testApp.exit() - this.setRotation(Surface.ROTATION_0) - } - test { - if (device.hasPipWindow()) { - device.closePipWindow() - } - } - } - transitions { - testApp.clickEnterPipButton() - device.expandPipWindow() - } - assertions { + val baseConfig = getTransitionLaunch( + eachRun = true, stringExtras = emptyMap()) + val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration -> + transitions { + pipApp.clickEnterPipButton() + pipApp.expandPipWindow(wmHelper) + } + assertions { + presubmit { windowManagerTrace { navBarWindowIsAlwaysVisible() statusBarWindowIsAlwaysVisible() all("pipWindowBecomesVisible") { - this.showsAppWindow(testApp.`package`) - .then() - .showsAppWindow(PIP_WINDOW_TITLE) + this.showsAppWindow(pipApp.defaultWindowName) } } layersTrace { - navBarLayerIsAlwaysVisible(bugId = 140855415) statusBarLayerIsAlwaysVisible() - noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0, - enabled = false) - navBarLayerRotatesAndScales(configuration.startRotation, - Surface.ROTATION_0, bugId = 140855415) statusBarLayerRotatesScales(configuration.startRotation, Surface.ROTATION_0) } layersTrace { all("pipLayerBecomesVisible") { - this.showsLayer(testApp.launcherName) - .then() - .showsLayer(PIP_WINDOW_TITLE) + this.showsLayer(pipApp.launcherName) } } } + + flaky { + layersTrace { + navBarLayerIsAlwaysVisible(bugId = 140855415) + noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0) + navBarLayerRotatesAndScales(configuration.startRotation, + Surface.ROTATION_0, bugId = 140855415) + } + } } + } + + return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig, + testSpec, supportedRotations = listOf(Surface.ROTATION_0), + repetitions = 5) } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt new file mode 100644 index 000000000000..eaaa2f6390be --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import android.view.Surface +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerTestRunner +import com.android.server.wm.flicker.FlickerTestRunnerFactory +import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.wm.shell.flicker.helpers.FixedAppHelper +import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.android.wm.shell.flicker.pip.PipTransitionBase.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE +import com.android.wm.shell.flicker.pip.PipTransitionBase.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT +import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP +import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test Pip with orientation changes. + * To run this test: `atest WMShellFlickerTests:PipOrientationTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class EnterPipToOtherOrientationTest( + testSpec: FlickerTestRunnerFactory.TestSpec +) : FlickerTestRunner(testSpec) { + companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) { + private val testApp = FixedAppHelper(instrumentation) + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<Array<Any>> { + return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, + supportedRotations = listOf(Surface.ROTATION_0), + repetitions = 5) { configuration -> + setupAndTeardown(this, configuration) + + setup { + eachRun { + // Launch a portrait only app on the fullscreen stack + testApp.launchViaIntent(wmHelper, stringExtras = mapOf( + EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())) + // Launch the PiP activity fixed as landscape + pipApp.launchViaIntent(wmHelper, stringExtras = mapOf( + EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())) + } + } + teardown { + eachRun { + pipApp.exit() + testApp.exit() + } + } + transitions { + // Enter PiP, and assert that the PiP is within bounds now that the device is back + // in portrait + broadcastActionTrigger.doAction(ACTION_ENTER_PIP) + wmHelper.waitPipWindowShown() + wmHelper.waitForAppTransitionIdle() + } + assertions { + val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) + val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) + + presubmit { + windowManagerTrace { + all("pipApp window is always on top") { + showsAppWindowOnTop(pipApp.defaultWindowName) + } + start("pipApp window hides testApp") { + isInvisible(testApp.defaultWindowName) + } + end("testApp windows is shown") { + isVisible(testApp.defaultWindowName) + } + navBarWindowIsAlwaysVisible() + statusBarWindowIsAlwaysVisible() + } + + layersTrace { + start("pipApp layer hides testApp") { + hasVisibleRegion(pipApp.defaultWindowName, startingBounds) + isInvisible(testApp.defaultWindowName) + } + } + } + + flaky { + layersTrace { + end("testApp layer covers fullscreen") { + hasVisibleRegion(testApp.defaultWindowName, endingBounds) + } + navBarLayerIsAlwaysVisible(bugId = 140855415) + statusBarLayerIsAlwaysVisible(bugId = 140855415) + } + } + } + } + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt new file mode 100644 index 000000000000..707d28d9c4c0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/Extensions.kt @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import android.app.WindowConfiguration +import android.content.ComponentName +import com.android.server.wm.flicker.traces.windowmanager.WindowManagerStateSubject +import com.android.server.wm.traces.common.windowmanager.WindowManagerState +import com.android.server.wm.traces.parser.toWindowName +import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.google.common.truth.Truth + +inline val WindowManagerState.pinnedWindows + get() = visibleWindows + .filter { it.windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED } + +/** + * Checks if the state has any window in PIP mode + */ +fun WindowManagerState.hasPipWindow(): Boolean = pinnedWindows.isNotEmpty() + +/** + * Checks that an activity [activity] is in PIP mode + */ +fun WindowManagerState.isInPipMode(activity: ComponentName): Boolean { + val windowName = activity.toWindowName() + return pinnedWindows.any { it.title == windowName } +} + +/** + * Asserts that an activity [activity] exists and is in PIP mode + */ +fun WindowManagerStateSubject.isInPipMode( + activity: ComponentName +): WindowManagerStateSubject = apply { + val windowName = activity.toWindowName() + hasWindow(windowName) + val pinnedWindows = wmState.pinnedWindows + .map { it.title } + Truth.assertWithMessage("Window not in PIP mode") + .that(pinnedWindows) + .contains(windowName) +} + +/** + * Waits until the state has a window in PIP mode, i.e., with + * windowingMode = WindowConfiguration.WINDOWING_MODE_PINNED + */ +fun WindowManagerStateHelper.waitPipWindowShown(): Boolean = + waitFor("PIP window shown") { + val result = it.wmState.hasPipWindow() + result + } + +/** + * Waits until the state doesn't have a window in PIP mode, i.e., with + * windowingMode = WindowConfiguration.WINDOWING_MODE_PINNED + */ +fun WindowManagerStateHelper.waitPipWindowGone(): Boolean = + waitFor("PIP window gone") { + val result = !it.wmState.hasPipWindow() + result + } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt index c21b594246b9..7576e24ace19 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTest.kt @@ -16,21 +16,19 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Presubmit +import android.os.Bundle import android.view.Surface import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerTestRunner +import com.android.server.wm.flicker.FlickerTestRunnerFactory import com.android.server.wm.flicker.dsl.FlickerBuilder -import com.android.server.wm.flicker.dsl.runWithFlicker import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.closePipWindow -import com.android.server.wm.flicker.helpers.hasPipWindow -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper +import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.startRotation import com.android.wm.shell.flicker.IME_WINDOW_NAME import com.android.wm.shell.flicker.helpers.ImeAppHelper -import com.android.wm.shell.flicker.testapp.Components import org.junit.FixMethodOrder -import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters import org.junit.runners.Parameterized @@ -39,114 +37,61 @@ import org.junit.runners.Parameterized * Test Pip launch. * To run this test: `atest WMShellFlickerTests:PipKeyboardTest` */ -@Presubmit @RequiresDevice @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -class PipKeyboardTest( - rotationName: String, - rotation: Int -) : PipTestBase(rotationName, rotation) { - private val keyboardApp = ImeAppHelper(instrumentation) - private val keyboardComponent = Components.ImeActivity.COMPONENT - private val helper = WindowManagerStateHelper() +class PipKeyboardTest(testSpec: FlickerTestRunnerFactory.TestSpec) : FlickerTestRunner(testSpec) { + companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) { + private const val TAG_IME_VISIBLE = "imeIsVisible" - private val keyboardScenario: FlickerBuilder - get() = FlickerBuilder(instrumentation).apply { - repeat { TEST_REPETITIONS } - // disable layer tracing - withLayerTracing { null } - setup { - test { - device.wakeUpAndGoToHomeScreen() - device.pressHome() - // launch our target pip app - testApp.launchViaIntent(wmHelper) - this.setRotation(rotation) - testApp.clickEnterPipButton() - // open an app with an input field and a keyboard - // UiAutomator doesn't support to launch the multiple Activities in a task. - // So use launchActivity() for the Keyboard Activity. - keyboardApp.launchViaIntent() - helper.waitForAppTransitionIdle() - helper.waitForFullScreenApp(keyboardComponent) - } - } - teardown { - test { - keyboardApp.exit() - - if (device.hasPipWindow()) { - device.closePipWindow() + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<Array<Any>> { + val imeApp = ImeAppHelper(instrumentation) + val baseConfig = getTransitionLaunch(eachRun = false) + val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration -> + setup { + test { + imeApp.launchViaIntent(wmHelper) + setRotation(configuration.startRotation) } - testApp.exit() - this.setRotation(Surface.ROTATION_0) } - } - } - - /** Ensure the pip window remains visible throughout any keyboard interactions. */ - @Test - fun pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose() { - val testTag = "pipWindow_doesNotLeaveTheScreen_onKeyboardOpenClose" - runWithFlicker(keyboardScenario) { - withTestName { testTag } - transitions { - // open the soft keyboard - keyboardApp.openIME(device, wmHelper) - helper.waitImeWindowShown() - - // then close it again - keyboardApp.closeIME(device, wmHelper) - helper.waitImeWindowGone() - } - assertions { - windowManagerTrace { - all("PiP window must remain inside visible bounds") { - val displayBounds = WindowUtils.getDisplayBounds(rotation) - coversAtMostRegion(testApp.defaultWindowName, displayBounds) + teardown { + test { + imeApp.exit() + setRotation(Surface.ROTATION_0) } } - } - } - } + transitions { + // open the soft keyboard + imeApp.openIME(wmHelper) + createTag(TAG_IME_VISIBLE) - /** Ensure the pip window does not obscure the keyboard. */ - @Test - fun pipWindow_doesNotObscure_keyboard() { - val testTag = "pipWindow_doesNotObscure_keyboard" - runWithFlicker(keyboardScenario) { - withTestName { testTag } - transitions { - // open the soft keyboard - keyboardApp.openIME(device, wmHelper) - helper.waitImeWindowShown() - } - teardown { - eachRun { - // close the keyboard - keyboardApp.closeIME(device, wmHelper) - helper.waitImeWindowGone() + // then close it again + imeApp.closeIME(wmHelper) } - } - assertions { - windowManagerTrace { - end("imeWindowAboveApp") { - isAboveWindow(IME_WINDOW_NAME, testApp.defaultWindowName) + assertions { + presubmit { + windowManagerTrace { + // Ensure the pip window remains visible throughout + // any keyboard interactions + all("pipInVisibleBounds") { + val displayBounds = WindowUtils.getDisplayBounds( + configuration.startRotation) + coversAtMostRegion(pipApp.defaultWindowName, displayBounds) + } + // Ensure that the pip window does not obscure the keyboard + tag(TAG_IME_VISIBLE) { + isAboveWindow(IME_WINDOW_NAME, pipApp.defaultWindowName) + } + } } } } - } - } - - companion object { - private const val TEST_REPETITIONS = 5 - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<Array<Any>> { - val supportedRotations = intArrayOf(Surface.ROTATION_0) - return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) } + return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, + baseConfig, testSpec, supportedRotations = listOf(Surface.ROTATION_0), + repetitions = 5) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt index e5790962c025..f10bd7f1e45a 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt @@ -32,6 +32,7 @@ import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.android.wm.shell.flicker.removeAllTasksButHome import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP import org.junit.FixMethodOrder import org.junit.Test diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt deleted file mode 100644 index 5e0760ceeda7..000000000000 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipOrientationTest.kt +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.wm.shell.flicker.pip - -import android.content.Intent -import android.platform.test.annotations.Presubmit -import android.view.Surface -import androidx.test.filters.RequiresDevice -import com.android.server.wm.flicker.dsl.runFlicker -import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.wm.shell.flicker.helpers.FixedAppHelper -import com.android.wm.shell.flicker.helpers.PipAppHelper -import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible -import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible -import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP -import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION -import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP -import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_PIP_ORIENTATION -import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION -import org.junit.Assert.assertEquals -import org.junit.FixMethodOrder -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.MethodSorters -import org.junit.runners.Parameterized - -/** - * Test Pip with orientation changes. - * To run this test: `atest WMShellFlickerTests:PipOrientationTest` - */ -@Presubmit -@RequiresDevice -@RunWith(Parameterized::class) -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -class PipOrientationTest( - rotationName: String, - rotation: Int -) : AppTestBase(rotationName, rotation) { - // Helper class to process test actions by broadcast. - private inner class BroadcastActionTrigger { - private fun createIntentWithAction(broadcastAction: String): Intent { - return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND) - } - fun doAction(broadcastAction: String) { - instrumentation.getContext().sendBroadcast(createIntentWithAction(broadcastAction)) - } - fun requestOrientationForPip(orientation: Int) { - instrumentation.getContext() - .sendBroadcast(createIntentWithAction(ACTION_SET_REQUESTED_ORIENTATION) - .putExtra(EXTRA_PIP_ORIENTATION, orientation.toString())) - } - } - private val broadcastActionTrigger = BroadcastActionTrigger() - - // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE - private val ORIENTATION_LANDSCAPE = 0 - // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - private val ORIENTATION_PORTRAIT = 1 - - private val testApp = FixedAppHelper(instrumentation) - private val pipApp = PipAppHelper(instrumentation) - - @Test - fun testEnterPipToOtherOrientation() { - runFlicker(instrumentation) { - withTestName { "testEnterPipToOtherOrientation" } - setup { - test { - removeAllTasksButHome() - device.wakeUpAndGoToHomeScreen() - // Launch a portrait only app on the fullscreen stack - testApp.launchViaIntent(stringExtras = mapOf( - EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())) - waitForAnimationComplete() - // Launch the PiP activity fixed as landscape - pipApp.launchViaIntent(stringExtras = mapOf( - EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())) - waitForAnimationComplete() - } - } - transitions { - // Enter PiP, and assert that the PiP is within bounds now that the device is back - // in portrait - broadcastActionTrigger.doAction(ACTION_ENTER_PIP) - waitForAnimationComplete() - } - teardown { - test { - removeAllTasksButHome() - } - } - assertions { - windowManagerTrace { - all("pipApp window is always on top") { - showsAppWindowOnTop(pipApp.defaultWindowName) - } - start("pipApp window hides testApp") { - isInvisible(testApp.defaultWindowName) - } - end("testApp windows is shown") { - isVisible(testApp.defaultWindowName) - } - navBarWindowIsAlwaysVisible() - statusBarWindowIsAlwaysVisible() - } - layersTrace { - val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) - val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) - start("pipApp layer hides testApp") { - hasVisibleRegion(pipApp.defaultWindowName, startingBounds) - isInvisible(testApp.defaultWindowName) - } - end("testApp layer covers fullscreen", enabled = false) { - hasVisibleRegion(testApp.defaultWindowName, endingBounds) - } - navBarLayerIsAlwaysVisible(bugId = 140855415) - statusBarLayerIsAlwaysVisible(bugId = 140855415) - } - } - } - } - - @Test - fun testSetRequestedOrientationWhilePinned() { - runFlicker(instrumentation) { - withTestName { "testSetRequestedOrientationWhilePinned" } - setup { - test { - removeAllTasksButHome() - device.wakeUpAndGoToHomeScreen() - // Launch the PiP activity fixed as landscape - pipApp.launchViaIntent(stringExtras = mapOf( - EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString(), - EXTRA_ENTER_PIP to "true")) - waitForAnimationComplete() - assertEquals(Surface.ROTATION_0, device.displayRotation) - } - } - transitions { - // Request that the orientation is set to landscape - broadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE) - - // Launch the activity back into fullscreen and ensure that it is now in landscape - pipApp.launchViaIntent() - waitForAnimationComplete() - assertEquals(Surface.ROTATION_90, device.displayRotation) - } - teardown { - test { - removeAllTasksButHome() - } - } - assertions { - val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) - val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) - windowManagerTrace { - start("PIP window must remain inside display") { - coversAtMostRegion(pipApp.defaultWindowName, startingBounds) - } - end("pipApp shows on top") { - showsAppWindowOnTop(pipApp.defaultWindowName) - } - navBarWindowIsAlwaysVisible() - statusBarWindowIsAlwaysVisible() - } - layersTrace { - start("PIP layer must remain inside display") { - coversAtMostRegion(startingBounds, pipApp.defaultWindowName) - } - end("pipApp layer covers fullscreen") { - hasVisibleRegion(pipApp.defaultWindowName, endingBounds) - } - navBarLayerIsAlwaysVisible(bugId = 140855415) - statusBarLayerIsAlwaysVisible(bugId = 140855415) - } - } - } - } - - companion object { - @Parameterized.Parameters(name = "{0}") - @JvmStatic - fun getParams(): Collection<Array<Any>> { - val supportedRotations = intArrayOf(Surface.ROTATION_0) - return supportedRotations.map { arrayOf(Surface.rotationToString(it), it) } - } - } -}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt index a00c5f463a50..adab5e81b32d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt @@ -16,21 +16,18 @@ package com.android.wm.shell.flicker.pip -import android.platform.test.annotations.Presubmit +import android.os.Bundle import android.view.Surface import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerTestRunner import com.android.server.wm.flicker.FlickerTestRunnerFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.endRotation import com.android.server.wm.flicker.helpers.WindowUtils -import com.android.server.wm.flicker.helpers.buildTestTag import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation import com.android.wm.shell.flicker.helpers.FixedAppHelper -import com.android.wm.shell.flicker.helpers.PipAppHelper import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible @@ -38,7 +35,6 @@ import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible import com.android.server.wm.flicker.noUncoveredRegions import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.statusBarLayerRotatesScales -import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -48,80 +44,77 @@ import org.junit.runners.Parameterized * Test Pip Stack in bounds after rotations. * To run this test: `atest WMShellFlickerTests:PipRotationTest` */ -@Presubmit @RequiresDevice @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) class PipRotationTest( testSpec: FlickerTestRunnerFactory.TestSpec ) : FlickerTestRunner(testSpec) { - companion object { + companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) { @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<Array<Any>> { - val instrumentation = InstrumentationRegistry.getInstrumentation() - val testApp = FixedAppHelper(instrumentation) - val pipApp = PipAppHelper(instrumentation) - return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation, - supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90)) { - configuration -> - withTestName { buildTestTag("PipRotationTest", testApp, configuration) } - repeat { configuration.repetitions } - setup { - test { - AppTestBase.removeAllTasksButHome() - device.wakeUpAndGoToHomeScreen() - pipApp.launchViaIntent(stringExtras = mapOf( - EXTRA_ENTER_PIP to "true")) - testApp.launchViaIntent() - AppTestBase.waitForAnimationComplete() - } - eachRun { - setRotation(configuration.startRotation) - } - } - transitions { - setRotation(configuration.endRotation) + val fixedApp = FixedAppHelper(instrumentation) + val baseConfig = getTransitionLaunch(eachRun = false) + val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration -> + setup { + test { + fixedApp.launchViaIntent(wmHelper) + } + eachRun { + setRotation(configuration.startRotation) + } + } + transitions { + setRotation(configuration.endRotation) + } + teardown { + eachRun { + setRotation(Surface.ROTATION_0) + } + } + assertions { + val startingBounds = WindowUtils.getDisplayBounds(configuration.startRotation) + val endingBounds = WindowUtils.getDisplayBounds(configuration.endRotation) + + presubmit { + windowManagerTrace { + navBarWindowIsAlwaysVisible() + statusBarWindowIsAlwaysVisible() } - teardown { - eachRun { - setRotation(Surface.ROTATION_0) - } - test { - AppTestBase.removeAllTasksButHome() - } + + layersTrace { + noUncoveredRegions(configuration.startRotation, + configuration.endRotation, allStates = false) } - assertions { - windowManagerTrace { - navBarWindowIsAlwaysVisible() - statusBarWindowIsAlwaysVisible() - } - layersTrace { - navBarLayerIsAlwaysVisible(bugId = 140855415) - statusBarLayerIsAlwaysVisible(bugId = 140855415) - noUncoveredRegions(configuration.startRotation, - configuration.endRotation, allStates = false) - navBarLayerRotatesAndScales(configuration.startRotation, - configuration.endRotation, bugId = 140855415) - statusBarLayerRotatesScales(configuration.startRotation, - configuration.endRotation, bugId = 140855415) + } + + flaky { + layersTrace { + navBarLayerIsAlwaysVisible(bugId = 140855415) + statusBarLayerIsAlwaysVisible(bugId = 140855415) + navBarLayerRotatesAndScales(configuration.startRotation, + configuration.endRotation, bugId = 140855415) + statusBarLayerRotatesScales(configuration.startRotation, + configuration.endRotation, bugId = 140855415) + + start("appLayerRotates_StartingBounds", bugId = 140855415) { + hasVisibleRegion(fixedApp.defaultWindowName, startingBounds) + coversAtMostRegion(startingBounds, pipApp.defaultWindowName) } - layersTrace { - val startingBounds = WindowUtils.getDisplayBounds( - configuration.startRotation) - val endingBounds = WindowUtils.getDisplayBounds( - configuration.endRotation) - start("appLayerRotates_StartingBounds", bugId = 140855415) { - hasVisibleRegion(testApp.defaultWindowName, startingBounds) - coversAtMostRegion(startingBounds, pipApp.defaultWindowName) - } - end("appLayerRotates_EndingBounds", bugId = 140855415) { - hasVisibleRegion(testApp.defaultWindowName, endingBounds) - coversAtMostRegion(endingBounds, pipApp.defaultWindowName) - } + end("appLayerRotates_EndingBounds", bugId = 140855415) { + hasVisibleRegion(fixedApp.defaultWindowName, endingBounds) + coversAtMostRegion(endingBounds, pipApp.defaultWindowName) } } } + } + } + + return FlickerTestRunnerFactory.getInstance().buildRotationTest(instrumentation, + baseConfig, testSpec, + supportedRotations = listOf(Surface.ROTATION_0, Surface.ROTATION_90), + repetitions = 5) } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt index 3e7eb134e627..4b826ffd646d 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToAppTest.kt @@ -16,29 +16,23 @@ package com.android.wm.shell.flicker.pip +import android.os.Bundle import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerTestRunner import com.android.server.wm.flicker.FlickerTestRunnerFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.focusChanges -import com.android.server.wm.flicker.helpers.buildTestTag -import com.android.server.wm.flicker.helpers.closePipWindow -import com.android.server.wm.flicker.helpers.expandPipWindow -import com.android.server.wm.flicker.helpers.hasPipWindow import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.wm.shell.flicker.helpers.PipAppHelper import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -51,48 +45,30 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@FlakyTest(bugId = 152738416) class PipToAppTest( testSpec: FlickerTestRunnerFactory.TestSpec ) : FlickerTestRunner(testSpec) { - companion object { + companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) { @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): List<Array<Any>> { - val instrumentation = InstrumentationRegistry.getInstrumentation() - val testApp = PipAppHelper(instrumentation) - return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, - supportedRotations = listOf(Surface.ROTATION_0)) { configuration -> - withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) } - repeat { configuration.repetitions } - setup { - test { - device.wakeUpAndGoToHomeScreen() - device.pressHome() - testApp.launchViaIntent(wmHelper) - } - eachRun { - this.setRotation(configuration.startRotation) - testApp.clickEnterPipButton() - device.hasPipWindow() - } - } - teardown { - eachRun { - this.setRotation(Surface.ROTATION_0) - } - test { - if (device.hasPipWindow()) { - device.closePipWindow() - } - testApp.exit() - } + val baseConfig = getTransitionLaunch(eachRun = true) + val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration -> + setup { + eachRun { + this.setRotation(configuration.startRotation) } - transitions { - device.expandPipWindow() - device.waitForIdle() + } + teardown { + eachRun { + this.setRotation(Surface.ROTATION_0) } - assertions { + } + transitions { + pipApp.expandPipWindowToApp(wmHelper) + } + assertions { + presubmit { windowManagerTrace { navBarWindowIsAlwaysVisible() statusBarWindowIsAlwaysVisible() @@ -100,34 +76,42 @@ class PipToAppTest( all("appReplacesPipWindow") { this.showsAppWindow(PIP_WINDOW_TITLE) .then() - .showsAppWindowOnTop(testApp.launcherName) + .showsAppWindowOnTop(pipApp.launcherName) } } layersTrace { - navBarLayerIsAlwaysVisible(bugId = 140855415) statusBarLayerIsAlwaysVisible() - noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0, - enabled = false) - navBarLayerRotatesAndScales(configuration.startRotation, - Surface.ROTATION_0, bugId = 140855415) statusBarLayerRotatesScales(configuration.startRotation, Surface.ROTATION_0) all("appReplacesPipLayer") { this.showsLayer(PIP_WINDOW_TITLE) .then() - .showsLayer(testApp.launcherName) + .showsLayer(pipApp.launcherName) } } + } + + flaky { + layersTrace { + navBarLayerIsAlwaysVisible(bugId = 140855415) + noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0) + navBarLayerRotatesAndScales(configuration.startRotation, + Surface.ROTATION_0, bugId = 140855415) + } eventLog { focusChanges( - "NexusLauncherActivity", testApp.launcherName, + "NexusLauncherActivity", pipApp.launcherName, "NexusLauncherActivity", bugId = 151179149) } } } + } + + return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig, + testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) } } } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt index 5d3bc1388686..62e82212b1d1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipToHomeTest.kt @@ -16,28 +16,23 @@ package com.android.wm.shell.flicker.pip +import android.os.Bundle import android.view.Surface -import androidx.test.filters.FlakyTest import androidx.test.filters.RequiresDevice import androidx.test.platform.app.InstrumentationRegistry import com.android.server.wm.flicker.FlickerTestRunner import com.android.server.wm.flicker.FlickerTestRunnerFactory +import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.focusChanges -import com.android.server.wm.flicker.helpers.buildTestTag -import com.android.server.wm.flicker.helpers.closePipWindow -import com.android.server.wm.flicker.helpers.hasPipWindow import com.android.server.wm.flicker.helpers.setRotation -import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible import com.android.server.wm.flicker.navBarLayerRotatesAndScales import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible import com.android.server.wm.flicker.noUncoveredRegions -import com.android.server.wm.flicker.repetitions import com.android.server.wm.flicker.startRotation import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible import com.android.server.wm.flicker.statusBarLayerRotatesScales import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible -import com.android.wm.shell.flicker.helpers.PipAppHelper import org.junit.FixMethodOrder import org.junit.runner.RunWith import org.junit.runners.MethodSorters @@ -50,50 +45,30 @@ import org.junit.runners.Parameterized @RequiresDevice @RunWith(Parameterized::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) -@FlakyTest(bugId = 152738416) class PipToHomeTest( testSpec: FlickerTestRunnerFactory.TestSpec ) : FlickerTestRunner(testSpec) { - companion object { + companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) { @Parameterized.Parameters(name = "{0}") @JvmStatic fun getParams(): List<Array<Any>> { - val instrumentation = InstrumentationRegistry.getInstrumentation() - val testApp = PipAppHelper(instrumentation) - return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, - supportedRotations = listOf(Surface.ROTATION_0)) { configuration -> - withTestName { buildTestTag("exitPipModeToApp", testApp, configuration) } - repeat { configuration.repetitions } - setup { - test { - device.wakeUpAndGoToHomeScreen() - device.pressHome() - } - eachRun { - testApp.launchViaIntent(wmHelper) - this.setRotation(configuration.startRotation) - testApp.clickEnterPipButton() - device.hasPipWindow() - } - } - teardown { - eachRun { - this.setRotation(Surface.ROTATION_0) - if (device.hasPipWindow()) { - device.closePipWindow() - } - } - test { - if (device.hasPipWindow()) { - device.closePipWindow() - } - testApp.exit() - } + val baseConfig = getTransitionLaunch(eachRun = true) + val testSpec: FlickerBuilder.(Bundle) -> Unit = { configuration -> + setup { + eachRun { + this.setRotation(configuration.startRotation) } - transitions { - testApp.closePipWindow() + } + teardown { + eachRun { + this.setRotation(Surface.ROTATION_0) } - assertions { + } + transitions { + pipApp.closePipWindow(wmHelper) + } + assertions { + presubmit { windowManagerTrace { navBarWindowIsAlwaysVisible() statusBarWindowIsAlwaysVisible() @@ -106,12 +81,7 @@ class PipToHomeTest( } layersTrace { - navBarLayerIsAlwaysVisible(bugId = 140855415) statusBarLayerIsAlwaysVisible() - noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0, - enabled = false) - navBarLayerRotatesAndScales(configuration.startRotation, - Surface.ROTATION_0, bugId = 140855415) statusBarLayerRotatesScales(configuration.startRotation, Surface.ROTATION_0) @@ -121,13 +91,28 @@ class PipToHomeTest( .hidesLayer(PIP_WINDOW_TITLE) } } + } + postsubmit { + layersTrace { + navBarLayerIsAlwaysVisible() + noUncoveredRegions(configuration.startRotation, Surface.ROTATION_0) + navBarLayerRotatesAndScales(configuration.startRotation, + Surface.ROTATION_0) + } + } + + flaky { eventLog { - focusChanges(testApp.launcherName, "NexusLauncherActivity", + focusChanges(pipApp.launcherName, "NexusLauncherActivity", bugId = 151179149) } } } + } + + return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, baseConfig, + testSpec, supportedRotations = listOf(Surface.ROTATION_0), repetitions = 5) } } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt new file mode 100644 index 000000000000..eb7bae160577 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransitionBase.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import android.app.Instrumentation +import android.content.Intent +import android.os.Bundle +import android.view.Surface +import com.android.server.wm.flicker.dsl.FlickerBuilder +import com.android.server.wm.flicker.helpers.buildTestTag +import com.android.server.wm.flicker.helpers.closePipWindow +import com.android.server.wm.flicker.helpers.hasPipWindow +import com.android.server.wm.flicker.helpers.setRotation +import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen +import com.android.server.wm.flicker.repetitions +import com.android.wm.shell.flicker.helpers.PipAppHelper +import com.android.wm.shell.flicker.removeAllTasksButHome +import com.android.wm.shell.flicker.testapp.Components + +abstract class PipTransitionBase(protected val instrumentation: Instrumentation) { + // Helper class to process test actions by broadcast. + protected class BroadcastActionTrigger(private val instrumentation: Instrumentation) { + private fun createIntentWithAction(broadcastAction: String): Intent { + return Intent(broadcastAction).setFlags(Intent.FLAG_RECEIVER_FOREGROUND) + } + + fun doAction(broadcastAction: String) { + instrumentation.context + .sendBroadcast(createIntentWithAction(broadcastAction)) + } + + fun requestOrientationForPip(orientation: Int) { + instrumentation.context.sendBroadcast( + createIntentWithAction(Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION) + .putExtra(Components.PipActivity.EXTRA_PIP_ORIENTATION, orientation.toString()) + ) + } + + companion object { + // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + @JvmStatic + val ORIENTATION_LANDSCAPE = 0 + + // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + @JvmStatic + val ORIENTATION_PORTRAIT = 1 + } + } + + protected val pipApp = PipAppHelper(instrumentation) + protected val broadcastActionTrigger = BroadcastActionTrigger(instrumentation) + + /** + * Gets a configuration that handles basic setup and teardown of pip tests + */ + protected val setupAndTeardown: FlickerBuilder.(Bundle) -> Unit + get() = { configuration -> + withTestName { buildTestTag(configuration) } + repeat { configuration.repetitions } + setup { + test { + removeAllTasksButHome() + device.wakeUpAndGoToHomeScreen() + } + } + teardown { + eachRun { + setRotation(Surface.ROTATION_0) + } + test { + removeAllTasksButHome() + + if (device.hasPipWindow()) { + device.closePipWindow() + } + pipApp.exit() + } + } + } + + /** + * Gets a configuration that handles basic setup and teardown of pip tests and that + * launches the Pip app for test + * + * @param eachRun If the pip app should be launched in each run (otherwise only 1x per test) + * @param stringExtras Arguments to pass to the PIP launch intent + */ + @JvmOverloads + fun getTransitionLaunch( + eachRun: Boolean, + stringExtras: Map<String, String> = mapOf(Components.PipActivity.EXTRA_ENTER_PIP to "true") + ): FlickerBuilder.(Bundle) -> Unit { + return { configuration -> + setupAndTeardown(this, configuration) + + setup { + test { + removeAllTasksButHome() + if (!eachRun) { + pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras) + wmHelper.waitPipWindowShown() + } + } + eachRun { + if (eachRun) { + pipApp.launchViaIntent(wmHelper, stringExtras = stringExtras) + wmHelper.waitPipWindowShown() + } + } + } + teardown { + eachRun { + if (eachRun) { + pipApp.exit() + } + } + test { + if (!eachRun) { + pipApp.exit() + } + removeAllTasksButHome() + } + } + } + } +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt new file mode 100644 index 000000000000..c01bc94151e9 --- /dev/null +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.flicker.pip + +import android.view.Surface +import androidx.test.filters.RequiresDevice +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.wm.flicker.FlickerTestRunner +import com.android.server.wm.flicker.FlickerTestRunnerFactory +import com.android.server.wm.flicker.helpers.WindowUtils +import com.android.server.wm.flicker.navBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.navBarWindowIsAlwaysVisible +import com.android.server.wm.flicker.statusBarLayerIsAlwaysVisible +import com.android.server.wm.flicker.statusBarWindowIsAlwaysVisible +import com.android.wm.shell.flicker.pip.PipTransitionBase.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE +import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION +import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP +import org.junit.Assert.assertEquals +import org.junit.FixMethodOrder +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import org.junit.runners.Parameterized + +/** + * Test Pip with orientation changes. + * To run this test: `atest WMShellFlickerTests:PipOrientationTest` + */ +@RequiresDevice +@RunWith(Parameterized::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class SetRequestedOrientationWhilePinnedTest( + testSpec: FlickerTestRunnerFactory.TestSpec +) : FlickerTestRunner(testSpec) { + companion object : PipTransitionBase(InstrumentationRegistry.getInstrumentation()) { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun getParams(): Collection<Array<Any>> { + return FlickerTestRunnerFactory.getInstance().buildTest(instrumentation, + supportedRotations = listOf(Surface.ROTATION_0), + repetitions = 1) { configuration -> + setupAndTeardown(this, configuration) + + setup { + eachRun { + // Launch the PiP activity fixed as landscape + pipApp.launchViaIntent(wmHelper, stringExtras = mapOf( + EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString(), + EXTRA_ENTER_PIP to "true")) + } + } + teardown { + eachRun { + pipApp.exit() + } + } + transitions { + // Request that the orientation is set to landscape + broadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE) + + // Launch the activity back into fullscreen and + // ensure that it is now in landscape + pipApp.launchViaIntent(wmHelper) + wmHelper.waitForFullScreenApp(pipApp.component) + wmHelper.waitForRotation(Surface.ROTATION_90) + assertEquals(Surface.ROTATION_90, device.displayRotation) + } + assertions { + val startingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_0) + val endingBounds = WindowUtils.getDisplayBounds(Surface.ROTATION_90) + presubmit { + windowManagerTrace { + start("PIP window must remain inside display") { + coversAtMostRegion(pipApp.defaultWindowName, startingBounds) + } + end("pipApp shows on top") { + showsAppWindowOnTop(pipApp.defaultWindowName) + } + navBarWindowIsAlwaysVisible() + statusBarWindowIsAlwaysVisible() + } + layersTrace { + start("PIP layer must remain inside display") { + coversAtMostRegion(startingBounds, pipApp.defaultWindowName) + } + end("pipApp layer covers fullscreen") { + hasVisibleRegion(pipApp.defaultWindowName, endingBounds) + } + } + } + + flaky { + layersTrace { + navBarLayerIsAlwaysVisible(bugId = 140855415) + statusBarLayerIsAlwaysVisible(bugId = 140855415) + } + } + } + } + } + } +}
\ No newline at end of file diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h index 27be62269959..d5fee3f667a9 100644 --- a/libs/hwui/DeviceInfo.h +++ b/libs/hwui/DeviceInfo.h @@ -35,7 +35,6 @@ class DeviceInfo { public: static DeviceInfo* get(); - static float getMaxRefreshRate() { return get()->mMaxRefreshRate; } static int32_t getWidth() { return get()->mWidth; } static int32_t getHeight() { return get()->mHeight; } // Gets the density in density-independent pixels @@ -45,7 +44,6 @@ public: static int64_t getAppOffset() { return get()->mAppVsyncOffsetNanos; } // Sets the density in density-independent pixels static void setDensity(float density) { sDensity.store(density); } - static void setMaxRefreshRate(float refreshRate) { get()->mMaxRefreshRate = refreshRate; } static void setWidth(int32_t width) { get()->mWidth = width; } static void setHeight(int32_t height) { get()->mHeight = height; } static void setRefreshRate(float refreshRate) { @@ -91,7 +89,6 @@ private: SkColorType mWideColorType = SkColorType::kN32_SkColorType; int mDisplaysSize = 0; int mPhysicalDisplayIndex = -1; - float mMaxRefreshRate = 60.0; int32_t mWidth = 1080; int32_t mHeight = 1920; int64_t mVsyncPeriod = 16666666; diff --git a/libs/hwui/Properties.cpp b/libs/hwui/Properties.cpp index e798f2a2bc69..971a53a8b2dc 100644 --- a/libs/hwui/Properties.cpp +++ b/libs/hwui/Properties.cpp @@ -79,7 +79,6 @@ bool Properties::debuggingEnabled = false; bool Properties::isolatedProcess = false; int Properties::contextPriority = 0; -int Properties::defaultRenderAhead = -1; float Properties::defaultSdrWhitePoint = 200.f; bool Properties::load() { @@ -129,10 +128,6 @@ bool Properties::load() { runningInEmulator = base::GetBoolProperty(PROPERTY_QEMU_KERNEL, false); - defaultRenderAhead = std::max( - -1, - std::min(2, base::GetIntProperty(PROPERTY_RENDERAHEAD, render_ahead().value_or(-1)))); - return (prevDebugLayersUpdates != debugLayersUpdates) || (prevDebugOverdraw != debugOverdraw); } diff --git a/libs/hwui/Properties.h b/libs/hwui/Properties.h index 1639143ef87c..dcb79babad24 100644 --- a/libs/hwui/Properties.h +++ b/libs/hwui/Properties.h @@ -162,8 +162,6 @@ enum DebugLevel { */ #define PROPERTY_QEMU_KERNEL "ro.kernel.qemu" -#define PROPERTY_RENDERAHEAD "debug.hwui.render_ahead" - /////////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////////// @@ -247,8 +245,6 @@ public: static int contextPriority; - static int defaultRenderAhead; - static float defaultSdrWhitePoint; private: diff --git a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp index a146b64e29cc..4966bfa1c1e9 100644 --- a/libs/hwui/jni/android_graphics_HardwareRenderer.cpp +++ b/libs/hwui/jni/android_graphics_HardwareRenderer.cpp @@ -603,14 +603,12 @@ static void android_view_ThreadedRenderer_setDisplayDensityDpi(JNIEnv*, jclass, static void android_view_ThreadedRenderer_initDisplayInfo(JNIEnv*, jclass, jint physicalWidth, jint physicalHeight, jfloat refreshRate, - jfloat maxRefreshRate, jint wideColorDataspace, jlong appVsyncOffsetNanos, jlong presentationDeadlineNanos) { DeviceInfo::setWidth(physicalWidth); DeviceInfo::setHeight(physicalHeight); DeviceInfo::setRefreshRate(refreshRate); - DeviceInfo::setMaxRefreshRate(maxRefreshRate); DeviceInfo::setWideColorDataspace(static_cast<ADataSpace>(wideColorDataspace)); DeviceInfo::setAppVsyncOffsetNanos(appVsyncOffsetNanos); DeviceInfo::setPresentationDeadlineNanos(presentationDeadlineNanos); @@ -735,7 +733,7 @@ static const JNINativeMethod gMethods[] = { {"nSetForceDark", "(JZ)V", (void*)android_view_ThreadedRenderer_setForceDark}, {"nSetDisplayDensityDpi", "(I)V", (void*)android_view_ThreadedRenderer_setDisplayDensityDpi}, - {"nInitDisplayInfo", "(IIFFIJJ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo}, + {"nInitDisplayInfo", "(IIFIJJ)V", (void*)android_view_ThreadedRenderer_initDisplayInfo}, {"preload", "()V", (void*)android_view_ThreadedRenderer_preload}, }; diff --git a/libs/hwui/jni/fonts/Font.cpp b/libs/hwui/jni/fonts/Font.cpp index 3392dac02493..c8471a9b8feb 100644 --- a/libs/hwui/jni/fonts/Font.cpp +++ b/libs/hwui/jni/fonts/Font.cpp @@ -219,7 +219,7 @@ static jlong Font_getBufferAddress(CRITICAL_JNI_PARAMS_COMMA jlong fontPtr) { } // Critical Native -static jlong Font_getReleaseNativeFontFunc() { +static jlong Font_getReleaseNativeFontFunc(CRITICAL_JNI_PARAMS) { return reinterpret_cast<jlong>(releaseFont); } diff --git a/libs/hwui/jni/fonts/FontFamily.cpp b/libs/hwui/jni/fonts/FontFamily.cpp index 80964794efb2..b68213549938 100644 --- a/libs/hwui/jni/fonts/FontFamily.cpp +++ b/libs/hwui/jni/fonts/FontFamily.cpp @@ -101,13 +101,13 @@ static jint FontFamily_getVariant(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr) { } // CriticalNative -static jint FontFamily_getFontSize(jlong familyPtr) { +static jint FontFamily_getFontSize(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr) { FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(familyPtr); return family->family->getNumFonts(); } // CriticalNative -static jlong FontFamily_getFont(jlong familyPtr, jint index) { +static jlong FontFamily_getFont(CRITICAL_JNI_PARAMS_COMMA jlong familyPtr, jint index) { FontFamilyWrapper* family = reinterpret_cast<FontFamilyWrapper*>(familyPtr); std::shared_ptr<minikin::Font> font = family->family->getFontRef(index); return reinterpret_cast<jlong>(new FontWrapper(std::move(font))); diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp index 37a6ee71c4a6..65afcc3a2558 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -108,7 +108,6 @@ CanvasContext::CanvasContext(RenderThread& thread, bool translucent, RenderNode* rootRenderNode->makeRoot(); mRenderNodes.emplace_back(rootRenderNode); mProfiler.setDensity(DeviceInfo::getDensity()); - setRenderAheadDepth(Properties::defaultRenderAhead); } CanvasContext::~CanvasContext() { @@ -157,24 +156,17 @@ static void setBufferCount(ANativeWindow* window) { void CanvasContext::setSurface(ANativeWindow* window, bool enableTimeout) { ATRACE_CALL(); - if (mFixedRenderAhead) { - mRenderAheadCapacity = mRenderAheadDepth; - } else { - if (DeviceInfo::get()->getMaxRefreshRate() > 66.6f) { - mRenderAheadCapacity = 1; - } else { - mRenderAheadCapacity = 0; - } - } - if (window) { + int extraBuffers = 0; + native_window_get_extra_buffer_count(window, &extraBuffers); + mNativeSurface = std::make_unique<ReliableSurface>(window); mNativeSurface->init(); if (enableTimeout) { // TODO: Fix error handling & re-shorten timeout ANativeWindow_setDequeueTimeout(window, 4000_ms); } - mNativeSurface->setExtraBufferCount(mRenderAheadCapacity); + mNativeSurface->setExtraBufferCount(extraBuffers); } else { mNativeSurface = nullptr; } @@ -441,24 +433,6 @@ void CanvasContext::notifyFramePending() { mRenderThread.pushBackFrameCallback(this); } -void CanvasContext::setPresentTime() { - int64_t presentTime = NATIVE_WINDOW_TIMESTAMP_AUTO; - int renderAhead = 0; - const auto frameIntervalNanos = mRenderThread.timeLord().frameIntervalNanos(); - if (mFixedRenderAhead) { - renderAhead = std::min(mRenderAheadDepth, mRenderAheadCapacity); - } else if (frameIntervalNanos < 15_ms) { - renderAhead = std::min(1, static_cast<int>(mRenderAheadCapacity)); - } - - if (renderAhead) { - presentTime = mCurrentFrameInfo->get(FrameInfoIndex::Vsync) + - (frameIntervalNanos * (renderAhead + 1)) - DeviceInfo::get()->getAppOffset() + - (frameIntervalNanos / 2); - } - native_window_set_buffers_timestamp(mNativeSurface->getNativeWindow(), presentTime); -} - void CanvasContext::draw() { SkRect dirty; mDamageAccumulator.finish(&dirty); @@ -478,8 +452,6 @@ void CanvasContext::draw() { mCurrentFrameInfo->markIssueDrawCommandsStart(); Frame frame = mRenderPipeline->getFrame(); - setPresentTime(); - SkRect windowDirty = computeDirtyRect(frame, &dirty); bool drew = mRenderPipeline->draw(frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, @@ -765,19 +737,6 @@ bool CanvasContext::surfaceRequiresRedraw() { return width != mLastFrameWidth || height != mLastFrameHeight; } -void CanvasContext::setRenderAheadDepth(int renderAhead) { - if (renderAhead > 2 || renderAhead < -1 || mNativeSurface) { - return; - } - if (renderAhead == -1) { - mFixedRenderAhead = false; - mRenderAheadDepth = 0; - } else { - mFixedRenderAhead = true; - mRenderAheadDepth = static_cast<uint32_t>(renderAhead); - } -} - SkRect CanvasContext::computeDirtyRect(const Frame& frame, SkRect* dirty) { if (frame.width() != mLastFrameWidth || frame.height() != mLastFrameHeight) { // can't rely on prior content of window if viewport size changes diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h index cc4eb3285bbe..b31883b9ae94 100644 --- a/libs/hwui/renderthread/CanvasContext.h +++ b/libs/hwui/renderthread/CanvasContext.h @@ -193,9 +193,6 @@ public: return mUseForceDark; } - // Must be called before setSurface - void setRenderAheadDepth(int renderAhead); - SkISize getNextFrameSize() const; private: @@ -211,7 +208,6 @@ private: bool isSwapChainStuffed(); bool surfaceRequiresRedraw(); - void setPresentTime(); void setupPipelineSurface(); SkRect computeDirtyRect(const Frame& frame, SkRect* dirty); @@ -232,9 +228,6 @@ private: // painted onto its surface. bool mIsDirty = false; SwapBehavior mSwapBehavior = SwapBehavior::kSwap_default; - bool mFixedRenderAhead = false; - uint32_t mRenderAheadDepth = 0; - uint32_t mRenderAheadCapacity = 0; struct SwapHistory { SkRect damage; nsecs_t vsyncTime; diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp index b51f6dcfc66f..0ade8dde12eb 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -295,11 +295,6 @@ void RenderProxy::setForceDark(bool enable) { mRenderThread.queue().post([this, enable]() { mContext->setForceDark(enable); }); } -void RenderProxy::setRenderAheadDepth(int renderAhead) { - mRenderThread.queue().post( - [context = mContext, renderAhead] { context->setRenderAheadDepth(renderAhead); }); -} - int RenderProxy::copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom, SkBitmap* bitmap) { auto& thread = RenderThread::getInstance(); diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h index 33dabc9895b1..a4adb16a930e 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -123,23 +123,6 @@ public: void removeFrameMetricsObserver(FrameMetricsObserver* observer); void setForceDark(bool enable); - /** - * Sets a render-ahead depth on the backing renderer. This will increase latency by - * <swapInterval> * renderAhead and increase memory usage by (3 + renderAhead) * <resolution>. - * In return the renderer will be less susceptible to jitter, resulting in a smoother animation. - * - * Not recommended to use in response to anything touch driven, but for canned animations - * where latency is not a concern careful use may be beneficial. - * - * Note that when increasing this there will be a frame gap of N frames where N is - * renderAhead - <current renderAhead>. When decreasing this if there are any pending - * frames they will retain their prior renderAhead value, so it will take a few frames - * for the decrease to flush through. - * - * @param renderAhead How far to render ahead, must be in the range [0..2] - */ - void setRenderAheadDepth(int renderAhead); - static int copySurfaceInto(ANativeWindow* window, int left, int top, int right, int bottom, SkBitmap* bitmap); static void prepareToDraw(Bitmap& bitmap); diff --git a/libs/hwui/tests/common/TestScene.h b/libs/hwui/tests/common/TestScene.h index 74a039b3d090..91022cfe734b 100644 --- a/libs/hwui/tests/common/TestScene.h +++ b/libs/hwui/tests/common/TestScene.h @@ -38,7 +38,6 @@ public: int count = 0; int reportFrametimeWeight = 0; bool renderOffscreen = true; - int renderAhead = 0; }; template <class T> diff --git a/libs/hwui/tests/macrobench/TestSceneRunner.cpp b/libs/hwui/tests/macrobench/TestSceneRunner.cpp index eda5d2266dcf..8c7d2612f39b 100644 --- a/libs/hwui/tests/macrobench/TestSceneRunner.cpp +++ b/libs/hwui/tests/macrobench/TestSceneRunner.cpp @@ -153,11 +153,6 @@ void run(const TestScene::Info& info, const TestScene::Options& opts, proxy->resetProfileInfo(); proxy->fence(); - if (opts.renderAhead) { - usleep(33000); - } - proxy->setRenderAheadDepth(opts.renderAhead); - ModifiedMovingAverage<double> avgMs(opts.reportFrametimeWeight); nsecs_t start = systemTime(SYSTEM_TIME_MONOTONIC); diff --git a/libs/hwui/tests/macrobench/main.cpp b/libs/hwui/tests/macrobench/main.cpp index 88d33c315a09..174a14080eff 100644 --- a/libs/hwui/tests/macrobench/main.cpp +++ b/libs/hwui/tests/macrobench/main.cpp @@ -69,7 +69,6 @@ OPTIONS: are offscreen rendered --benchmark_format Set output format. Possible values are tabular, json, csv --renderer=TYPE Sets the render pipeline to use. May be skiagl or skiavk - --render-ahead=NUM Sets how far to render-ahead. Must be 0 (default), 1, or 2. )"); } @@ -171,7 +170,6 @@ enum { Onscreen, Offscreen, Renderer, - RenderAhead, }; } @@ -187,7 +185,6 @@ static const struct option LONG_OPTIONS[] = { {"onscreen", no_argument, nullptr, LongOpts::Onscreen}, {"offscreen", no_argument, nullptr, LongOpts::Offscreen}, {"renderer", required_argument, nullptr, LongOpts::Renderer}, - {"render-ahead", required_argument, nullptr, LongOpts::RenderAhead}, {0, 0, 0, 0}}; static const char* SHORT_OPTIONS = "c:r:h"; @@ -286,16 +283,6 @@ void parseOptions(int argc, char* argv[]) { gOpts.renderOffscreen = true; break; - case LongOpts::RenderAhead: - if (!optarg) { - error = true; - } - gOpts.renderAhead = atoi(optarg); - if (gOpts.renderAhead < 0 || gOpts.renderAhead > 2) { - error = true; - } - break; - case 'h': printHelp(); exit(EXIT_SUCCESS); diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java index d40de76431e4..205c1f4b4057 100644 --- a/media/java/android/media/AudioDeviceInfo.java +++ b/media/java/android/media/AudioDeviceInfo.java @@ -354,7 +354,7 @@ public final class AudioDeviceInfo { /** * @hide - * @return the internal device tyoe + * @return the internal device type */ public int getInternalType() { return mPort.type(); diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java index fd71670ee404..606cd5718d98 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/DeviceDiscoveryService.java @@ -322,7 +322,8 @@ public class DeviceDiscoveryService extends Service { void onDeviceSelected(String callingPackage, String deviceAddress) { mServiceCallback.complete(new Association( - getUserId(), deviceAddress, callingPackage, mRequest.getDeviceProfile(), false)); + getUserId(), deviceAddress, callingPackage, mRequest.getDeviceProfile(), false, + System.currentTimeMillis())); } void onCancel() { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index e38cf237d36b..5c943f63a9a1 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -245,9 +245,11 @@ interface ISystemUiProxy { void setSideStageVisibility(in boolean visible) = 36; /** Removes the split-screen stages. */ void exitSplitScreen() = 37; - void startTask(in int taskId, in int stage, in int position, in Bundle options) = 38; + /** @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible. */ + void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 38; + void startTask(in int taskId, in int stage, in int position, in Bundle options) = 39; void startShortcut(in String packageName, in String shortcutId, in int stage, in int position, - in Bundle options, in UserHandle user) = 39; + in Bundle options, in UserHandle user) = 40; void startIntent( - in PendingIntent intent, in int stage, in int position, in Bundle options) = 40; + in PendingIntent intent, in int stage, in int position, in Bundle options) = 41; } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt index 429f67fdc706..d06568a7caf9 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt @@ -24,6 +24,9 @@ import android.service.controls.Control */ interface ControlActionCoordinator { + // Handle actions launched from GlobalActionsDialog or ControlDialog + var startedFromGlobalActions: Boolean + /** * Close any dialogs which may have been open */ diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index 7cd7e18965c2..247f25e1ccea 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -30,6 +30,7 @@ import android.service.controls.actions.CommandAction import android.service.controls.actions.FloatAction import android.util.Log import android.view.HapticFeedbackConstants +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.globalactions.GlobalActionsComponent @@ -37,6 +38,7 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.concurrency.DelayableExecutor import com.android.wm.shell.TaskViewFactory +import dagger.Lazy import java.util.Optional import javax.inject.Inject @@ -48,13 +50,17 @@ class ControlActionCoordinatorImpl @Inject constructor( private val activityStarter: ActivityStarter, private val keyguardStateController: KeyguardStateController, private val globalActionsComponent: GlobalActionsComponent, - private val taskViewFactory: Optional<TaskViewFactory> + private val taskViewFactory: Optional<TaskViewFactory>, + private val broadcastDispatcher: BroadcastDispatcher, + private val lazyUiController: Lazy<ControlsUiController> ) : ControlActionCoordinator { private var dialog: Dialog? = null private val vibrator = context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator private var pendingAction: Action? = null private var actionsInProgress = mutableSetOf<String>() + override var startedFromGlobalActions: Boolean = true + companion object { private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L } @@ -131,8 +137,8 @@ class ControlActionCoordinatorImpl @Inject constructor( private fun bouncerOrRun(action: Action) { if (keyguardStateController.isShowing()) { - var closeGlobalActions = !keyguardStateController.isUnlocked() - if (closeGlobalActions) { + var closeDialog = !keyguardStateController.isUnlocked() + if (closeDialog) { context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) // pending actions will only run after the control state has been refreshed @@ -141,8 +147,12 @@ class ControlActionCoordinatorImpl @Inject constructor( activityStarter.dismissKeyguardThenExecute({ Log.d(ControlsUiController.TAG, "Device unlocked, invoking controls action") - if (closeGlobalActions) { - globalActionsComponent.handleShowGlobalActionsMenu() + if (closeDialog) { + if (startedFromGlobalActions) { + globalActionsComponent.handleShowGlobalActionsMenu() + } else { + ControlsDialog(context, broadcastDispatcher).show(lazyUiController.get()) + } } else { action.invoke() } diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt index 8e878cf76ad9..f533cfb47076 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialog.kt @@ -66,7 +66,7 @@ class ControlsDialog( val vg = requireViewById<ViewGroup>(com.android.systemui.R.id.global_actions_controls) vg.alpha = 0f - controller.show(vg, { /* do nothing */ }) + controller.show(vg, { /* do nothing */ }, false /* startedFromGlobalActions */) vg.animate() .alpha(1f) diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt index 4e4c82cabaa0..944887741721 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt @@ -29,7 +29,7 @@ interface ControlsUiController { public const val EXTRA_ANIMATE = "extra_animate" } - fun show(parent: ViewGroup, dismissGlobalActions: Runnable) + fun show(parent: ViewGroup, onDismiss: Runnable, startedFromGlobalActions: Boolean) fun hide() /** diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt index 2b529f9a6cde..762362cde095 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt @@ -102,7 +102,7 @@ class ControlsUiControllerImpl @Inject constructor ( private lateinit var lastItems: List<SelectionItem> private var popup: ListPopupWindow? = null private var hidden = true - private lateinit var dismissGlobalActions: Runnable + private lateinit var onDismiss: Runnable private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow) private var retainCache = false @@ -145,13 +145,19 @@ class ControlsUiControllerImpl @Inject constructor ( } } - override fun show(parent: ViewGroup, dismissGlobalActions: Runnable) { + override fun show( + parent: ViewGroup, + onDismiss: Runnable, + startedFromGlobalActions: Boolean + ) { Log.d(ControlsUiController.TAG, "show()") this.parent = parent - this.dismissGlobalActions = dismissGlobalActions + this.onDismiss = onDismiss hidden = false retainCache = false + controlActionCoordinator.startedFromGlobalActions = startedFromGlobalActions + allStructures = controlsController.get().getFavorites() selectedStructure = loadPreference(allStructures) @@ -187,7 +193,7 @@ class ControlsUiControllerImpl @Inject constructor ( controlViewsById.clear() controlsById.clear() - show(parent, dismissGlobalActions) + show(parent, onDismiss, controlActionCoordinator.startedFromGlobalActions) val showAnim = ObjectAnimator.ofFloat(parent, "alpha", 0.0f, 1.0f) showAnim.setInterpolator(DecelerateInterpolator(1.0f)) showAnim.setDuration(FADE_IN_MILLIS) @@ -260,7 +266,7 @@ class ControlsUiControllerImpl @Inject constructor ( private fun startActivity(context: Context, intent: Intent) { // Force animations when transitioning from a dialog to an activity intent.putExtra(ControlsUiController.EXTRA_ANIMATE, true) - dismissGlobalActions.run() + onDismiss.run() activityStarter.dismissKeyguardThenExecute({ shadeController.collapsePanel(false) diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java index ec4a91c24464..2b362b94d1f5 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java @@ -20,6 +20,7 @@ import android.app.Activity; import com.android.systemui.ForegroundServicesDialog; import com.android.systemui.keyguard.WorkLockActivity; +import com.android.systemui.people.PeopleSpaceActivity; import com.android.systemui.screenrecord.ScreenRecordDialog; import com.android.systemui.settings.brightness.BrightnessDialog; import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity; @@ -92,4 +93,10 @@ public abstract class DefaultActivityBinder { @IntoMap @ClassKey(TvNotificationPanelActivity.class) public abstract Activity bindTvNotificationPanelActivity(TvNotificationPanelActivity activity); + + /** Inject into PeopleSpaceActivity. */ + @Binds + @IntoMap + @ClassKey(PeopleSpaceActivity.class) + public abstract Activity bindPeopleSpaceActivity(PeopleSpaceActivity activity); } diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java index ad4c44767995..8af45a5c0ef1 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java @@ -2229,7 +2229,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, private void showControls(ControlsUiController controller) { mControlsUiController = controller; - mControlsUiController.show(mControlsView, this::dismissForControlsActivity); + mControlsUiController.show(mControlsView, this::dismissForControlsActivity, + true /* startedFromGlobalActions */); } private boolean isWalletViewAvailable() { @@ -2457,7 +2458,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return WindowInsets.CONSUMED; }); if (mControlsUiController != null) { - mControlsUiController.show(mControlsView, this::dismissForControlsActivity); + mControlsUiController.show(mControlsView, this::dismissForControlsActivity, + true /* startedFromGlobalActions */); } mBackgroundDrawable.setAlpha(0); @@ -2632,7 +2634,8 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, initializeLayout(); mGlobalActionsLayout.updateList(); if (mControlsUiController != null) { - mControlsUiController.show(mControlsView, this::dismissForControlsActivity); + mControlsUiController.show(mControlsView, this::dismissForControlsActivity, + true /* startedFromGlobalActions */); } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 5a918d4808d6..c55fdf4783e3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -67,6 +67,7 @@ import android.telephony.TelephonyManager; import android.util.EventLog; import android.util.Log; import android.util.Slog; +import android.util.SparseBooleanArray; import android.util.SparseIntArray; import android.view.IRemoteAnimationFinishedCallback; import android.view.RemoteAnimationTarget; @@ -306,6 +307,13 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, */ private final SparseIntArray mLastSimStates = new SparseIntArray(); + /** + * Indicates if a SIM card had the SIM PIN enabled during the initialization, before + * reaching the SIM_STATE_READY state. The flag is reset to false at SIM_STATE_READY. + * Index is the slotId - in case of multiple SIM cards. + */ + private final SparseBooleanArray mSimWasLocked = new SparseBooleanArray(); + private boolean mDeviceInteractive; private boolean mGoingToSleep; @@ -477,10 +485,10 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, } } - boolean simWasLocked; + boolean lastSimStateWasLocked; synchronized (KeyguardViewMediator.this) { int lastState = mLastSimStates.get(slotId); - simWasLocked = (lastState == TelephonyManager.SIM_STATE_PIN_REQUIRED + lastSimStateWasLocked = (lastState == TelephonyManager.SIM_STATE_PIN_REQUIRED || lastState == TelephonyManager.SIM_STATE_PUK_REQUIRED); mLastSimStates.append(slotId, simState); } @@ -504,17 +512,19 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, if (simState == TelephonyManager.SIM_STATE_ABSENT) { // MVNO SIMs can become transiently NOT_READY when switching networks, // so we should only lock when they are ABSENT. - if (simWasLocked) { + if (lastSimStateWasLocked) { if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to ABSENT when the " + "previous state was locked. Reset the state."); resetStateLocked(); } + mSimWasLocked.append(slotId, false); } } break; case TelephonyManager.SIM_STATE_PIN_REQUIRED: case TelephonyManager.SIM_STATE_PUK_REQUIRED: synchronized (KeyguardViewMediator.this) { + mSimWasLocked.append(slotId, true); if (!mShowing) { if (DEBUG_SIM_STATES) Log.d(TAG, "INTENT_VALUE_ICC_LOCKED and keygaurd isn't " @@ -541,9 +551,10 @@ public class KeyguardViewMediator extends SystemUI implements Dumpable, case TelephonyManager.SIM_STATE_READY: synchronized (KeyguardViewMediator.this) { if (DEBUG_SIM_STATES) Log.d(TAG, "READY, reset state? " + mShowing); - if (mShowing && simWasLocked) { + if (mShowing && mSimWasLocked.get(slotId, false)) { if (DEBUG_SIM_STATES) Log.d(TAG, "SIM moved to READY when the " - + "previous state was locked. Reset the state."); + + "previously was locked. Reset the state."); + mSimWasLocked.append(slotId, false); resetStateLocked(); } } diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java index 580cbcf8ce47..c67aef618652 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java @@ -37,9 +37,12 @@ import android.view.ViewGroup; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; import com.android.systemui.R; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import java.util.List; +import javax.inject.Inject; + /** * Shows the user their tiles for their priority People (go/live-status). */ @@ -54,10 +57,17 @@ public class PeopleSpaceActivity extends Activity { private LauncherApps mLauncherApps; private Context mContext; private AppWidgetManager mAppWidgetManager; + private NotificationEntryManager mNotificationEntryManager; private int mAppWidgetId; private boolean mShowSingleConversation; private UiEventLogger mUiEventLogger = new UiEventLoggerImpl(); + @Inject + public PeopleSpaceActivity(NotificationEntryManager notificationEntryManager) { + super(); + mNotificationEntryManager = notificationEntryManager; + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -91,8 +101,8 @@ public class PeopleSpaceActivity extends Activity { */ private void setTileViewsWithPriorityConversations() { try { - List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles( - mContext, mNotificationManager, mPeopleManager, mLauncherApps); + List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles(mContext, mNotificationManager, + mPeopleManager, mLauncherApps, mNotificationEntryManager); for (PeopleSpaceTile tile : tiles) { PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext, mPeopleSpaceLayout, tile.getId()); diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java index 994dc6d78507..dd054848aed2 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java @@ -60,9 +60,12 @@ import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.internal.util.ArrayUtils; import com.android.settingslib.utils.ThreadUtils; +import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.text.SimpleDateFormat; import java.time.Duration; @@ -137,7 +140,7 @@ public class PeopleSpaceUtils { /** Returns a list of map entries corresponding to user's conversations. */ public static List<PeopleSpaceTile> getTiles( Context context, INotificationManager notificationManager, IPeopleManager peopleManager, - LauncherApps launcherApps) + LauncherApps launcherApps, NotificationEntryManager notificationEntryManager) throws Exception { boolean showOnlyPriority = Settings.Global.getInt(context.getContentResolver(), Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 1; @@ -173,6 +176,8 @@ public class PeopleSpaceUtils { getSortedTiles(peopleManager, launcherApps, mergedStream); tiles.addAll(recentTiles); } + + tiles = augmentTilesFromVisibleNotifications(tiles, notificationEntryManager); return tiles; } @@ -258,7 +263,8 @@ public class PeopleSpaceUtils { ServiceManager.getService(Context.NOTIFICATION_SERVICE)), IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)), - context.getSystemService(LauncherApps.class)); + context.getSystemService(LauncherApps.class), + Dependency.get(NotificationEntryManager.class)); Optional<PeopleSpaceTile> entry = tiles.stream().filter( e -> e.getId().equals(shortcutId)).findFirst(); if (entry.isPresent()) { @@ -339,6 +345,41 @@ public class PeopleSpaceUtils { && storedUserId == userId; } + static List<PeopleSpaceTile> augmentTilesFromVisibleNotifications(List<PeopleSpaceTile> tiles, + NotificationEntryManager notificationEntryManager) { + if (notificationEntryManager == null) { + Log.w(TAG, "NotificationEntryManager is null"); + return tiles; + } + Map<String, NotificationEntry> visibleNotifications = notificationEntryManager + .getVisibleNotifications() + .stream() + .filter(entry -> entry.getRanking() != null + && entry.getRanking().getConversationShortcutInfo() != null) + .collect(Collectors.toMap(PeopleSpaceUtils::getKey, e -> e)); + if (DEBUG) { + Log.d(TAG, "Number of visible notifications:" + visibleNotifications.size()); + } + return tiles + .stream() + .map(entry -> augmentTileFromVisibleNotifications(entry, visibleNotifications)) + .collect(Collectors.toList()); + } + + static PeopleSpaceTile augmentTileFromVisibleNotifications(PeopleSpaceTile tile, + Map<String, NotificationEntry> visibleNotifications) { + String shortcutId = tile.getId(); + String packageName = tile.getPackageName(); + int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier(); + String key = getKey(shortcutId, packageName, userId); + if (!visibleNotifications.containsKey(key)) { + if (DEBUG) Log.d(TAG, "No existing notifications for key:" + key); + return tile; + } + if (DEBUG) Log.d(TAG, "Augmenting tile from visible notifications, key:" + key); + return augmentTileFromNotification(tile, visibleNotifications.get(key).getSbn()); + } + /** * If incoming notification changed tile, store the changes in the tile options. */ @@ -355,17 +396,7 @@ public class PeopleSpaceUtils { } if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) { if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId); - Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(sbn); - if (message == null) { - if (DEBUG) Log.i(TAG, "Notification doesn't have content, skipping."); - return; - } - storedTile = storedTile - .toBuilder() - .setNotificationKey(sbn.getKey()) - .setNotificationContent(message.getText()) - .setNotificationDataUri(message.getDataUri()) - .build(); + storedTile = augmentTileFromNotification(storedTile, sbn); } else { if (DEBUG) { Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId); @@ -380,6 +411,21 @@ public class PeopleSpaceUtils { updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, storedTile); } + static PeopleSpaceTile augmentTileFromNotification(PeopleSpaceTile tile, + StatusBarNotification sbn) { + Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(sbn); + if (message == null) { + if (DEBUG) Log.i(TAG, "Notification doesn't have content, skipping."); + return tile; + } + return tile + .toBuilder() + .setNotificationKey(sbn.getKey()) + .setNotificationContent(message.getText()) + .setNotificationDataUri(message.getDataUri()) + .build(); + } + private static void updateAppWidgetOptions(AppWidgetManager appWidgetManager, int appWidgetId, PeopleSpaceTile tile) { if (tile == null) { @@ -792,6 +838,16 @@ public class PeopleSpaceUtils { return lookupKeysWithBirthdaysToday; } + static String getKey(NotificationEntry entry) { + if (entry.getRanking() == null || entry.getRanking().getConversationShortcutInfo() == null + || entry.getSbn() == null || entry.getSbn().getUser() == null) { + return null; + } + return getKey(entry.getRanking().getConversationShortcutInfo().getId(), + entry.getSbn().getPackageName(), + entry.getSbn().getUser().getIdentifier()); + } + /** * Returns the uniquely identifying key for the conversation. * diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java index bee54e4dca0a..bee9889eaa4e 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java @@ -135,12 +135,14 @@ public class PeopleSpaceWidgetManager { try { String sbnShortcutId = sbn.getShortcutId(); if (sbnShortcutId == null) { + if (DEBUG) Log.d(TAG, "Sbn shortcut id is null"); return; } int[] widgetIds = mAppWidgetService.getAppWidgetIds( new ComponentName(mContext, PeopleSpaceWidgetProvider.class) ); if (widgetIds.length == 0) { + Log.d(TAG, "No app widget ids returned"); return; } SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); @@ -148,6 +150,7 @@ public class PeopleSpaceWidgetManager { String key = PeopleSpaceUtils.getKey(sbnShortcutId, sbn.getPackageName(), userId); Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); if (storedWidgetIds.isEmpty()) { + Log.d(TAG, "No stored widget ids"); return; } for (String widgetIdString : storedWidgetIds) { diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java index fb33affcbac5..80794cb64883 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetRemoteViewsFactory.java @@ -28,9 +28,11 @@ import android.util.Log; import android.widget.RemoteViews; import android.widget.RemoteViewsService; +import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.people.PeopleSpaceTileView; import com.android.systemui.people.PeopleSpaceUtils; +import com.android.systemui.statusbar.notification.NotificationEntryManager; import java.util.ArrayList; import java.util.List; @@ -42,6 +44,7 @@ public class PeopleSpaceWidgetRemoteViewsFactory implements RemoteViewsService.R private IPeopleManager mPeopleManager; private INotificationManager mNotificationManager; + private NotificationEntryManager mNotificationEntryManager; private PackageManager mPackageManager; private LauncherApps mLauncherApps; private List<PeopleSpaceTile> mTiles = new ArrayList<>(); @@ -56,6 +59,7 @@ public class PeopleSpaceWidgetRemoteViewsFactory implements RemoteViewsService.R if (DEBUG) Log.d(TAG, "onCreate called"); mNotificationManager = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); + mNotificationEntryManager = Dependency.get(NotificationEntryManager.class); mPackageManager = mContext.getPackageManager(); mPeopleManager = IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)); @@ -70,7 +74,7 @@ public class PeopleSpaceWidgetRemoteViewsFactory implements RemoteViewsService.R private void setTileViewsWithPriorityConversations() { try { mTiles = PeopleSpaceUtils.getTiles(mContext, mNotificationManager, - mPeopleManager, mLauncherApps); + mPeopleManager, mLauncherApps, mNotificationEntryManager); } catch (Exception e) { Log.e(TAG, "Couldn't retrieve conversations", e); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java index d248ab544656..c8edaec98ee4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSAnimator.java @@ -362,7 +362,7 @@ public class QSAnimator implements Callback, PageListener, Listener, OnLayoutCha if(view == parent || view == null) return; // Ignore tile pages as they can have some offset we don't want to take into account in // RTL. - if (!(view instanceof PagedTileLayout.TilePage)) { + if (!(view instanceof PagedTileLayout.TilePage || view instanceof SideLabelTileLayout)) { loc1[0] += view.getLeft(); loc1[1] += view.getTop(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 01a8c1c89f84..5b2a7e7ff617 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -620,6 +620,19 @@ public class OverviewProxyService extends CurrentUserTracker implements } @Override + public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) { + if (!verifyCaller("exitSplitScreenOnHide")) { + return; + } + final long token = Binder.clearCallingIdentity(); + try { + mSplitScreenOptional.ifPresent(s -> s.exitSplitScreenOnHide(exitSplitScreenOnHide)); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void startTask(int taskId, int stage, int position, Bundle options) { if (!verifyCaller("startTask")) { return; diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java index 6db21f9159ec..4ee2759028a9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java @@ -17,6 +17,7 @@ package com.android.systemui.people; import static com.android.systemui.people.PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE; +import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME; import static com.google.common.truth.Truth.assertThat; @@ -52,6 +53,7 @@ import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; +import android.os.UserHandle; import android.provider.ContactsContract; import android.provider.Settings; import android.service.notification.ConversationChannelWrapper; @@ -64,6 +66,9 @@ import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.SbnBuilder; +import com.android.systemui.statusbar.notification.NotificationEntryManager; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import org.junit.Before; import org.junit.Test; @@ -82,10 +87,17 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { private static final int WIDGET_ID_WITH_SHORTCUT = 1; private static final int WIDGET_ID_WITHOUT_SHORTCUT = 2; - private static final String SHORTCUT_ID = "101"; + private static final String SHORTCUT_ID_1 = "101"; + private static final String SHORTCUT_ID_2 = "202"; + private static final String SHORTCUT_ID_3 = "303"; + private static final String SHORTCUT_ID_4 = "404"; private static final String NOTIFICATION_KEY = "notification_key"; private static final String NOTIFICATION_CONTENT = "notification_content"; private static final String TEST_LOOKUP_KEY = "lookup_key"; + private static final String NOTIFICATION_TEXT_1 = "notification_text_1"; + private static final String NOTIFICATION_TEXT_2 = "notification_text_2"; + private static final String NOTIFICATION_TEXT_3 = "notification_text_3"; + private static final String NOTIFICATION_TEXT_4 = "notification_text_4"; private static final int TEST_COLUMN_INDEX = 1; private static final Uri URI = Uri.parse("fake_uri"); private static final Icon ICON = Icon.createWithResource("package", R.drawable.ic_android); @@ -97,20 +109,66 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { .build(); private static final PeopleSpaceTile PERSON_TILE = new PeopleSpaceTile - .Builder(SHORTCUT_ID, "username", ICON, new Intent()) + .Builder(SHORTCUT_ID_1, "username", ICON, new Intent()) .setNotificationKey(NOTIFICATION_KEY) .setNotificationContent(NOTIFICATION_CONTENT) .setNotificationDataUri(URI) .build(); private final ShortcutInfo mShortcutInfo = new ShortcutInfo.Builder(mContext, - SHORTCUT_ID).setLongLabel( + SHORTCUT_ID_1).setLongLabel( "name").setPerson(PERSON) .build(); private final ShortcutInfo mShortcutInfoWithoutPerson = new ShortcutInfo.Builder(mContext, - SHORTCUT_ID).setLongLabel( + SHORTCUT_ID_1).setLongLabel( "name") .build(); + private final Notification mNotification1 = new Notification.Builder(mContext, "test") + .setContentTitle("TEST_TITLE") + .setContentText("TEST_TEXT") + .setShortcutId(SHORTCUT_ID_1) + .setStyle(new Notification.MessagingStyle(PERSON) + .addMessage(new Notification.MessagingStyle.Message( + NOTIFICATION_TEXT_1, 0, PERSON)) + .addMessage(new Notification.MessagingStyle.Message( + NOTIFICATION_TEXT_2, 20, PERSON)) + .addMessage(new Notification.MessagingStyle.Message( + NOTIFICATION_TEXT_3, 10, PERSON)) + ) + .build(); + private final Notification mNotification2 = new Notification.Builder(mContext, "test2") + .setContentTitle("TEST_TITLE") + .setContentText("OTHER_TEXT") + .setShortcutId(SHORTCUT_ID_2) + .setStyle(new Notification.MessagingStyle(PERSON) + .addMessage(new Notification.MessagingStyle.Message( + NOTIFICATION_TEXT_4, 0, PERSON)) + ) + .build(); + private final Notification mNotification3 = new Notification.Builder(mContext, "test2") + .setContentTitle("TEST_TITLE") + .setContentText("OTHER_TEXT") + .setShortcutId(SHORTCUT_ID_3) + .setStyle(new Notification.MessagingStyle(PERSON)) + .build(); + private final NotificationEntry mNotificationEntry1 = new NotificationEntryBuilder() + .setNotification(mNotification1) + .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_1).build()) + .setUser(UserHandle.of(0)) + .setPkg(PACKAGE_NAME) + .build(); + private final NotificationEntry mNotificationEntry2 = new NotificationEntryBuilder() + .setNotification(mNotification2) + .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_2).build()) + .setUser(UserHandle.of(0)) + .setPkg(PACKAGE_NAME) + .build(); + private final NotificationEntry mNotificationEntry3 = new NotificationEntryBuilder() + .setNotification(mNotification3) + .setShortcutInfo(new ShortcutInfo.Builder(mContext, SHORTCUT_ID_3).build()) + .setUser(UserHandle.of(0)) + .setPkg(PACKAGE_NAME) + .build(); @Mock private NotificationListener mListenerService; @@ -130,6 +188,8 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { private ContentResolver mMockContentResolver; @Mock private Context mMockContext; + @Mock + private NotificationEntryManager mNotificationEntryManager; @Before public void setUp() throws RemoteException { @@ -152,15 +212,17 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { isNull())).thenReturn(mMockCursor); when(mMockContext.getString(R.string.birthday_status)).thenReturn( mContext.getString(R.string.birthday_status)); + when(mNotificationEntryManager.getVisibleNotifications()) + .thenReturn(List.of(mNotificationEntry1, mNotificationEntry2, mNotificationEntry3)); } @Test public void testGetTilesReturnsSortedListWithMultipleRecentConversations() throws Exception { // Ensure the less-recent Important conversation is before more recent conversations. ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper( - SHORTCUT_ID, false, 3); + SHORTCUT_ID_1, false, 3); ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper( - SHORTCUT_ID + 1, + SHORTCUT_ID_1 + 1, true, 1); when(mNotificationManager.getConversations(anyBoolean())).thenReturn( new ParceledListSlice(Arrays.asList( @@ -169,9 +231,9 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { // Ensure the non-Important conversation is sorted between these recent conversations. ConversationChannel recentConversationBeforeNonImportantConversation = getConversationChannel( - SHORTCUT_ID + 2, 4); + SHORTCUT_ID_1 + 2, 4); ConversationChannel recentConversationAfterNonImportantConversation = - getConversationChannel(SHORTCUT_ID + 3, + getConversationChannel(SHORTCUT_ID_1 + 3, 2); when(mPeopleManager.getRecentConversations()).thenReturn( new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation, @@ -179,7 +241,8 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { List<String> orderedShortcutIds = PeopleSpaceUtils.getTiles( mContext, mNotificationManager, mPeopleManager, - mLauncherApps).stream().map(tile -> tile.getId()).collect(Collectors.toList()); + mLauncherApps, mNotificationEntryManager) + .stream().map(tile -> tile.getId()).collect(Collectors.toList()); assertThat(orderedShortcutIds).containsExactly( // Even though the oldest conversation, should be first since "important" @@ -196,11 +259,11 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { throws Exception { // Ensure the less-recent Important conversation is before more recent conversations. ConversationChannelWrapper newerNonImportantConversation = getConversationChannelWrapper( - SHORTCUT_ID, false, 3); + SHORTCUT_ID_1, false, 3); ConversationChannelWrapper newerImportantConversation = getConversationChannelWrapper( - SHORTCUT_ID + 1, true, 3); + SHORTCUT_ID_1 + 1, true, 3); ConversationChannelWrapper olderImportantConversation = getConversationChannelWrapper( - SHORTCUT_ID + 2, + SHORTCUT_ID_1 + 2, true, 1); when(mNotificationManager.getConversations(anyBoolean())).thenReturn( new ParceledListSlice(Arrays.asList( @@ -210,9 +273,9 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { // Ensure the non-Important conversation is sorted between these recent conversations. ConversationChannel recentConversationBeforeNonImportantConversation = getConversationChannel( - SHORTCUT_ID + 3, 4); + SHORTCUT_ID_1 + 3, 4); ConversationChannel recentConversationAfterNonImportantConversation = - getConversationChannel(SHORTCUT_ID + 4, + getConversationChannel(SHORTCUT_ID_1 + 4, 2); when(mPeopleManager.getRecentConversations()).thenReturn( new ParceledListSlice(Arrays.asList(recentConversationAfterNonImportantConversation, @@ -220,7 +283,8 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { List<String> orderedShortcutIds = PeopleSpaceUtils.getTiles( mContext, mNotificationManager, mPeopleManager, - mLauncherApps).stream().map(tile -> tile.getId()).collect(Collectors.toList()); + mLauncherApps, mNotificationEntryManager) + .stream().map(tile -> tile.getId()).collect(Collectors.toList()); assertThat(orderedShortcutIds).containsExactly( // Important conversations should be sorted at the beginning. @@ -238,7 +302,7 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { Notification notification = new Notification.Builder(mContext, "test") .setContentTitle("TEST_TITLE") .setContentText("TEST_TEXT") - .setShortcutId(SHORTCUT_ID) + .setShortcutId(SHORTCUT_ID_1) .build(); StatusBarNotification sbn = new SbnBuilder() .setNotification(notification) @@ -341,24 +405,126 @@ public class PeopleSpaceUtilsTest extends SysuiTestCase { @Test public void testGetLastMessagingStyleMessage() { - Notification notification = new Notification.Builder(mContext, "test") - .setContentTitle("TEST_TITLE") - .setContentText("TEST_TEXT") - .setShortcutId(SHORTCUT_ID) - .setStyle(new Notification.MessagingStyle(PERSON) - .addMessage(new Notification.MessagingStyle.Message("text1", 0, PERSON)) - .addMessage(new Notification.MessagingStyle.Message("text2", 20, PERSON)) - .addMessage(new Notification.MessagingStyle.Message("text3", 10, PERSON)) - ) - .build(); StatusBarNotification sbn = new SbnBuilder() - .setNotification(notification) + .setNotification(mNotification1) .build(); Notification.MessagingStyle.Message lastMessage = PeopleSpaceUtils.getLastMessagingStyleMessage(sbn); - assertThat(lastMessage.getText()).isEqualTo("text2"); + assertThat(lastMessage.getText().toString()).isEqualTo(NOTIFICATION_TEXT_2); + } + + @Test + public void testAugmentTileFromNotification() { + StatusBarNotification sbn = new SbnBuilder() + .setNotification(mNotification1) + .build(); + + PeopleSpaceTile tile = + new PeopleSpaceTile + .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent()) + .setPackageName(PACKAGE_NAME) + .setUid(0) + .build(); + PeopleSpaceTile actual = PeopleSpaceUtils + .augmentTileFromNotification(tile, sbn); + + assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2); + } + + @Test + public void testAugmentTileFromNotificationNoContent() { + StatusBarNotification sbn = new SbnBuilder() + .setNotification(mNotification3) + .build(); + + PeopleSpaceTile tile = + new PeopleSpaceTile + .Builder(SHORTCUT_ID_3, "userName", ICON, new Intent()) + .setPackageName(PACKAGE_NAME) + .setUid(0) + .build(); + PeopleSpaceTile actual = PeopleSpaceUtils + .augmentTileFromNotification(tile, sbn); + + assertThat(actual.getNotificationKey()).isEqualTo(null); + assertThat(actual.getNotificationContent()).isEqualTo(null); + } + + @Test + public void testAugmentTileFromVisibleNotifications() { + PeopleSpaceTile tile = + new PeopleSpaceTile + .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent()) + .setPackageName(PACKAGE_NAME) + .setUid(0) + .build(); + PeopleSpaceTile actual = PeopleSpaceUtils + .augmentTileFromVisibleNotifications(tile, + Map.of(PeopleSpaceUtils.getKey(mNotificationEntry1), mNotificationEntry1)); + + assertThat(actual.getNotificationContent().toString()).isEqualTo(NOTIFICATION_TEXT_2); + } + + @Test + public void testAugmentTileFromVisibleNotificationsDifferentShortcutId() { + PeopleSpaceTile tile = + new PeopleSpaceTile + .Builder(SHORTCUT_ID_4, "userName", ICON, new Intent()) + .setPackageName(PACKAGE_NAME) + .setUid(0) + .build(); + PeopleSpaceTile actual = PeopleSpaceUtils + .augmentTileFromVisibleNotifications(tile, + Map.of(PeopleSpaceUtils.getKey(mNotificationEntry1), mNotificationEntry1)); + + assertThat(actual.getNotificationContent()).isEqualTo(null); + } + + @Test + public void testAugmentTilesFromVisibleNotificationsSingleTile() { + PeopleSpaceTile tile = + new PeopleSpaceTile + .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent()) + .setPackageName(PACKAGE_NAME) + .setUid(0) + .build(); + List<PeopleSpaceTile> actualList = PeopleSpaceUtils + .augmentTilesFromVisibleNotifications(List.of(tile), mNotificationEntryManager); + + assertThat(actualList.size()).isEqualTo(1); + assertThat(actualList.get(0).getNotificationContent().toString()) + .isEqualTo(NOTIFICATION_TEXT_2); + + verify(mNotificationEntryManager, times(1)).getVisibleNotifications(); + } + + @Test + public void testAugmentTilesFromVisibleNotificationsMultipleTiles() { + PeopleSpaceTile tile1 = + new PeopleSpaceTile + .Builder(SHORTCUT_ID_1, "userName", ICON, new Intent()) + .setPackageName(PACKAGE_NAME) + .setUid(1) + .build(); + PeopleSpaceTile tile2 = + new PeopleSpaceTile + .Builder(SHORTCUT_ID_2, "userName2", ICON, new Intent()) + .setPackageName(PACKAGE_NAME) + .setUid(0) + .build(); + List<PeopleSpaceTile> actualList = PeopleSpaceUtils + .augmentTilesFromVisibleNotifications(List.of(tile1, tile2), + mNotificationEntryManager); + + assertThat(actualList.size()).isEqualTo(2); + assertThat(actualList.get(0).getNotificationContent().toString()) + .isEqualTo(NOTIFICATION_TEXT_2); + assertThat(actualList.get(1).getNotificationContent().toString()) + .isEqualTo(NOTIFICATION_TEXT_4); + + verify(mNotificationEntryManager, times(1)).getVisibleNotifications(); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java index ef314ad16556..9470141178dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java @@ -421,6 +421,7 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { throws Exception { int[] widgetIdsArray = {WIDGET_ID_WITH_SHORTCUT, WIDGET_ID_WITHOUT_SHORTCUT}; when(mIAppWidgetService.getAppWidgetIds(any())).thenReturn(widgetIdsArray); + setStorageForTile(SHORTCUT_ID, TEST_PACKAGE_A, WIDGET_ID_WITH_SHORTCUT); Notification notificationWithoutMessagingStyle = new Notification.Builder(mContext) .setContentTitle("TEST_TITLE") @@ -429,15 +430,21 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { .build(); StatusBarNotification sbn = new SbnBuilder() .setNotification(notificationWithoutMessagingStyle) + .setPkg(TEST_PACKAGE_A) + .setUid(0) .build(); NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder() .setSbn(sbn) .setId(1)); mClock.advanceTime(MIN_LINGER_DURATION); - verify(mAppWidgetManager, never()) - .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), any()); - verify(mAppWidgetManager, never()).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), + verify(mAppWidgetManager, times(1)) + .updateAppWidgetOptions(eq(WIDGET_ID_WITH_SHORTCUT), + mBundleArgumentCaptor.capture()); + Bundle options = requireNonNull(mBundleArgumentCaptor.getValue()); + assertThat((PeopleSpaceTile) options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE)) + .isEqualTo(PERSON_TILE); + verify(mAppWidgetManager, times(1)).updateAppWidget(eq(WIDGET_ID_WITH_SHORTCUT), any()); } diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java index 25f4abe2cb14..903a07140eae 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -19,12 +19,12 @@ package com.android.server.accessibility.gestures; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP; -import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP; import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP; +import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD; import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP; @@ -147,15 +147,15 @@ class GestureManifold implements GestureMatcher.StateChangeListener { mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP, this)); mMultiFingerGestures.add( - new MultiFingerMultiTapAndHold( - mContext, 2, 1, GESTURE_2_FINGER_SINGLE_TAP_AND_HOLD, this)); - mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP, this)); mMultiFingerGestures.add( new MultiFingerMultiTapAndHold( mContext, 2, 2, GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD, this)); mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP, this)); + mMultiFingerGestures.add( + new MultiFingerMultiTapAndHold( + mContext, 2, 3, GESTURE_2_FINGER_TRIPLE_TAP_AND_HOLD, this)); // Three-finger taps. mMultiFingerGestures.add( new MultiFingerMultiTap(mContext, 3, 1, GESTURE_3_FINGER_SINGLE_TAP, this)); diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 67f654e6360f..6d72ca7bae25 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -1492,7 +1492,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } final Dataset dataset = (Dataset) result; final Dataset oldDataset = authenticatedResponse.getDatasets().get(datasetIdx); - if (!isPinnedDataset(oldDataset)) { + if (!isAuthResultDatasetEphemeral(oldDataset, data)) { authenticatedResponse.getDatasets().set(datasetIdx, dataset); } autoFill(requestId, datasetIdx, dataset, false); @@ -1513,6 +1513,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** + * Returns whether the dataset returned from the authentication result is ephemeral or not. + * See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more + * information. + */ + private static boolean isAuthResultDatasetEphemeral(@Nullable Dataset oldDataset, + @NonNull Bundle authResultData) { + if (authResultData.containsKey( + AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) { + return authResultData.getBoolean( + AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET); + } + return isPinnedDataset(oldDataset); + } + + /** * A dataset can potentially have multiple fields, and it's possible that some of the fields' * has inline presentation and some don't. It's also possible that some of the fields' * inline presentation is pinned and some isn't. So the concept of whether a dataset is diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 96b69dcc45d6..10b00d38fb42 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -161,6 +161,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind private static final boolean DEBUG = false; private static final String LOG_TAG = "CompanionDeviceManagerService"; + private static final long PAIR_WITHOUT_PROMPT_WINDOW_MS = 10 * 60 * 1000; // 10 min + private static final String PREF_FILE_NAME = "companion_device_preferences.xml"; private static final String PREF_KEY_AUTO_REVOKE_GRANTS_DONE = "auto_revoke_grants_done"; @@ -170,6 +172,7 @@ public class CompanionDeviceManagerService extends SystemService implements Bind private static final String XML_ATTR_DEVICE = "device"; private static final String XML_ATTR_PROFILE = "profile"; private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby"; + private static final String XML_ATTR_TIME_APPROVED = "time_approved"; private static final String XML_FILE_NAME = "companion_device_manager_associations.xml"; private final CompanionDeviceManagerImpl mImpl; @@ -606,7 +609,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind association.getDeviceMacAddress(), association.getPackageName(), association.getDeviceProfile(), - active /* notifyOnDeviceNearby */); + active, /* notifyOnDeviceNearby */ + association.getTimeApprovedMs()); } else { return association; } @@ -639,6 +643,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } @Override + public boolean canPairWithoutPrompt( + String packageName, String deviceMacAddress, int userId) { + return CollectionUtils.any( + getAllAssociations(userId, packageName, deviceMacAddress), + a -> System.currentTimeMillis() - a.getTimeApprovedMs() + < PAIR_WITHOUT_PROMPT_WINDOW_MS); + } + + @Override public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, String[] args, ShellCallback callback, ResultReceiver resultReceiver) throws RemoteException { @@ -877,6 +890,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind Boolean.toString( association.isNotifyOnDeviceNearby())); } + tag.attribute(null, XML_ATTR_TIME_APPROVED, + Long.toString(association.getTimeApprovedMs())); tag.endTag(null, XML_TAG_ASSOCIATION); }); @@ -921,7 +936,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } - @Nullable private Set<Association> getAllAssociations(int userId, @Nullable String packageFilter) { return CollectionUtils.filter( getAllAssociations(userId), @@ -941,6 +955,14 @@ public class CompanionDeviceManagerService extends SystemService implements Bind } } + private Set<Association> getAllAssociations( + int userId, @Nullable String packageFilter, @Nullable String addressFilter) { + return CollectionUtils.filter( + getAllAssociations(userId), + a -> Objects.equals(packageFilter, a.getPackageName()) + && Objects.equals(addressFilter, a.getDeviceMacAddress())); + } + private Set<Association> readAllAssociations(int userId) { final AtomicFile file = getStorageFileForUser(userId); @@ -962,12 +984,14 @@ public class CompanionDeviceManagerService extends SystemService implements Bind final String profile = parser.getAttributeValue(null, XML_ATTR_PROFILE); final boolean persistentGrants = Boolean.valueOf( parser.getAttributeValue(null, XML_ATTR_NOTIFY_DEVICE_NEARBY)); + final long timeApproved = parseLongOrDefault( + parser.getAttributeValue(null, XML_ATTR_TIME_APPROVED), 0L); if (appPackage == null || deviceAddress == null) continue; result = ArrayUtils.add(result, new Association(userId, deviceAddress, appPackage, - profile, persistentGrants)); + profile, persistentGrants, timeApproved)); } return result; } catch (XmlPullParserException | IOException e) { @@ -1293,6 +1317,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind return result; } + private static long parseLongOrDefault(String str, long def) { + try { + return Long.parseLong(str); + } catch (NumberFormatException e) { + Log.w(LOG_TAG, "Failed to parse", e); + return def; + } + } + private class ShellCmd extends ShellCommand { public static final String USAGE = "help\n" + "list USER_ID\n" @@ -1321,7 +1354,8 @@ public class CompanionDeviceManagerService extends SystemService implements Bind int userId = getNextArgInt(); String pkg = getNextArgRequired(); String address = getNextArgRequired(); - addAssociation(new Association(userId, address, pkg, null, false)); + addAssociation(new Association(userId, address, pkg, null, false, + System.currentTimeMillis())); } break; diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java index 6886cdefc28a..737a9e4ff46c 100644 --- a/services/core/java/android/content/pm/PackageManagerInternal.java +++ b/services/core/java/android/content/pm/PackageManagerInternal.java @@ -30,6 +30,7 @@ import android.content.pm.PackageManager.ApplicationInfoFlags; import android.content.pm.PackageManager.ComponentInfoFlags; import android.content.pm.PackageManager.PackageInfoFlags; import android.content.pm.PackageManager.ResolveInfoFlags; +import android.content.pm.overlay.OverlayPaths; import android.content.pm.parsing.component.ParsedMainComponent; import android.os.Bundle; import android.os.Handler; @@ -49,8 +50,8 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.concurrent.Executor; import java.util.function.Consumer; @@ -535,17 +536,17 @@ public abstract class PackageManagerInternal { * Set which overlay to use for a package. * @param userId The user for which to update the overlays. * @param targetPackageName The package name of the package for which to update the overlays. - * @param overlayPackageNames The complete list of overlay packages that should be enabled for - * the target. Previously enabled overlays not specified in the list - * will be disabled. Pass in null or an empty list to disable - * all overlays. The order of the items is significant if several - * overlays modify the same resource. + * @param overlayPaths The complete list of overlay paths that should be enabled for + * the target. Previously enabled overlays not specified in the list + * will be disabled. Pass in null or empty paths to disable all overlays. + * The order of the items is significant if several overlays modify the + * same resource. * @param outUpdatedPackageNames An output list that contains the package names of packages * affected by the update of enabled overlays. * @return true if all packages names were known by the package manager, false otherwise */ public abstract boolean setEnabledOverlayPackages(int userId, String targetPackageName, - List<String> overlayPackageNames, Collection<String> outUpdatedPackageNames); + @Nullable OverlayPaths overlayPaths, Set<String> outUpdatedPackageNames); /** * Resolves an activity intent, allowing instant apps to be resolved. diff --git a/services/core/java/com/android/server/EntropyMixer.java b/services/core/java/com/android/server/EntropyMixer.java index c56cef2d58dc..a83c981235df 100644 --- a/services/core/java/com/android/server/EntropyMixer.java +++ b/services/core/java/com/android/server/EntropyMixer.java @@ -16,12 +16,6 @@ package com.android.server; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; - import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -33,10 +27,15 @@ import android.os.Message; import android.os.SystemProperties; import android.util.Slog; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; + /** * A service designed to load and periodically save "randomness" - * for the Linux kernel RNG and to mix in data from Hardware RNG (if present) - * into the Linux RNG. + * for the Linux kernel RNG. * * <p>When a Linux system starts up, the entropy pool associated with * {@code /dev/random} may be in a fairly predictable state. Applications which @@ -45,15 +44,8 @@ import android.util.Slog; * this effect, it's helpful to carry the entropy pool information across * shutdowns and startups. * - * <p>On systems with Hardware RNG (/dev/hw_random), a block of output from HW - * RNG is mixed into the Linux RNG on EntropyMixer's startup and whenever - * EntropyMixer periodically runs to save a block of output from Linux RNG on - * disk. This mixing is done in a way that does not increase the Linux RNG's - * entropy estimate is not increased. This is to avoid having to trust/verify - * the quality and authenticity of the "randomness" of the HW RNG. - * * <p>This class was modeled after the script in the - * <a href="http://www.kernel.org/doc/man-pages/online/pages/man4/random.4.html"> + * <a href="https://man7.org/linux/man-pages/man4/random.4.html"> * random(4) manual page</a>. */ public class EntropyMixer extends Binder { @@ -64,7 +56,6 @@ public class EntropyMixer extends Binder { private static final long START_NANOTIME = System.nanoTime(); private final String randomDevice; - private final String hwRandomDevice; private final String entropyFile; /** @@ -80,7 +71,6 @@ public class EntropyMixer extends Binder { Slog.e(TAG, "Will not process invalid message"); return; } - addHwRandomEntropy(); writeEntropy(); scheduleEntropyWriter(); } @@ -94,25 +84,21 @@ public class EntropyMixer extends Binder { }; public EntropyMixer(Context context) { - this(context, getSystemDir() + "/entropy.dat", "/dev/urandom", "/dev/hw_random"); + this(context, getSystemDir() + "/entropy.dat", "/dev/urandom"); } /** Test only interface, not for public use */ public EntropyMixer( Context context, String entropyFile, - String randomDevice, - String hwRandomDevice) { + String randomDevice) { if (randomDevice == null) { throw new NullPointerException("randomDevice"); } - if (hwRandomDevice == null) { throw new NullPointerException("hwRandomDevice"); } if (entropyFile == null) { throw new NullPointerException("entropyFile"); } this.randomDevice = randomDevice; - this.hwRandomDevice = hwRandomDevice; this.entropyFile = entropyFile; loadInitialEntropy(); addDeviceSpecificEntropy(); - addHwRandomEntropy(); writeEntropy(); scheduleEntropyWriter(); IntentFilter broadcastFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); @@ -192,23 +178,6 @@ public class EntropyMixer extends Binder { } } - /** - * Mixes in the output from HW RNG (if present) into the Linux RNG. - */ - private void addHwRandomEntropy() { - if (!new File(hwRandomDevice).exists()) { - // HW RNG not present/exposed -- ignore - return; - } - - try { - RandomBlock.fromFile(hwRandomDevice).toFile(randomDevice, false); - Slog.i(TAG, "Added HW RNG output to entropy pool"); - } catch (IOException e) { - Slog.w(TAG, "Failed to add HW RNG output to entropy pool", e); - } - } - private static String getSystemDir() { File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index c18031fd6de6..0afdbdecd610 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -1149,7 +1149,7 @@ public final class CachedAppOptimizer { } return; } - } catch (IOException e) { + } catch (Exception e) { Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid + "): " + e); return; @@ -1244,7 +1244,7 @@ public final class CachedAppOptimizer { } } } - } catch (IOException e) { + } catch (Exception e) { Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e); synchronized (mAm) { synchronized (mProcLock) { diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java index 3acad49496c5..76c34678d589 100644 --- a/services/core/java/com/android/server/app/GameManagerService.java +++ b/services/core/java/com/android/server/app/GameManagerService.java @@ -16,17 +16,23 @@ package com.android.server.app; +import android.Manifest; import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.app.ActivityManager; import android.app.GameManager; import android.app.GameManager.GameMode; import android.app.IGameManagerService; import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Binder; import android.os.Environment; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.Process; import android.util.ArrayMap; +import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -81,6 +87,14 @@ public final class GameManagerService extends IGameManagerService.Stub { switch (msg.what) { case WRITE_SETTINGS: { final int userId = (int) msg.obj; + if (userId < 0) { + Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId); + synchronized (mLock) { + removeMessages(WRITE_SETTINGS, msg.obj); + } + break; + } + Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT); synchronized (mLock) { removeMessages(WRITE_SETTINGS, msg.obj); @@ -94,6 +108,15 @@ public final class GameManagerService extends IGameManagerService.Stub { } case REMOVE_SETTINGS: { final int userId = (int) msg.obj; + if (userId < 0) { + Slog.wtf(TAG, "Attempt to write settings for invalid user: " + userId); + synchronized (mLock) { + removeMessages(WRITE_SETTINGS, msg.obj); + removeMessages(REMOVE_SETTINGS, msg.obj); + } + break; + } + synchronized (mLock) { // Since the user was removed, ignore previous write message // and do write here. @@ -146,9 +169,23 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - //TODO(b/178111358) Add proper permission check and multi-user handling + private boolean hasPermission(String permission) { + return mContext.checkCallingOrSelfPermission(permission) + == PackageManager.PERMISSION_GRANTED; + } + @Override + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public @GameMode int getGameMode(String packageName, int userId) { + if (!hasPermission(Manifest.permission.MANAGE_GAME_MODE)) { + Log.w(TAG, String.format("Caller or self does not have permission.MANAGE_GAME_MODE")); + return GameManager.GAME_MODE_UNSUPPORTED; + } + + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, false, true, "getGameMode", + "com.android.server.app.GameManagerService"); + synchronized (mLock) { if (!mSettings.containsKey(userId)) { return GameManager.GAME_MODE_UNSUPPORTED; @@ -158,9 +195,18 @@ public final class GameManagerService extends IGameManagerService.Stub { } } - //TODO(b/178111358) Add proper permission check and multi-user handling @Override + @RequiresPermission(Manifest.permission.MANAGE_GAME_MODE) public void setGameMode(String packageName, @GameMode int gameMode, int userId) { + if (!hasPermission(Manifest.permission.MANAGE_GAME_MODE)) { + Log.w(TAG, String.format("Caller or self does not have permission.MANAGE_GAME_MODE")); + return; + } + + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), + Binder.getCallingUid(), userId, false, true, "setGameMode", + "com.android.server.app.GameManagerService"); + synchronized (mLock) { if (!mSettings.containsKey(userId)) { return; diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java index ce24e5efdc07..de571863dbd4 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java @@ -69,6 +69,8 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide final List<BiometricAuthenticator.Identifier> unknownHALTemplates = ((InternalEnumerateClient<T>) mCurrentTask).getUnknownHALTemplates(); + Slog.d(TAG, "Enumerate onClientFinished: " + clientMonitor + ", success: " + success); + if (!unknownHALTemplates.isEmpty()) { Slog.w(TAG, "Adding " + unknownHALTemplates.size() + " templates for deletion"); } @@ -90,6 +92,7 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide private final Callback mRemoveCallback = new Callback() { @Override public void onClientFinished(@NonNull BaseClientMonitor clientMonitor, boolean success) { + Slog.d(TAG, "Remove onClientFinished: " + clientMonitor + ", success: " + success); mCallback.onClientFinished(InternalCleanupClient.this, success); } }; @@ -115,6 +118,8 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide } private void startCleanupUnknownHalTemplates() { + Slog.d(TAG, "startCleanupUnknownHalTemplates, size: " + mUnknownHALTemplates.size()); + UserTemplate template = mUnknownHALTemplates.get(0); mUnknownHALTemplates.remove(template); mCurrentTask = getRemovalClient(getContext(), mLazyDaemon, getToken(), @@ -138,6 +143,8 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide // Start enumeration. Removal will start if necessary, when enumeration is completed. mCurrentTask = getEnumerateClient(getContext(), mLazyDaemon, getToken(), getTargetUserId(), getOwnerString(), mEnrolledList, mBiometricUtils, getSensorId()); + + Slog.d(TAG, "Starting enumerate: " + mCurrentTask); mCurrentTask.start(mEnumerateCallback); } @@ -165,6 +172,7 @@ public abstract class InternalCleanupClient<S extends BiometricAuthenticator.Ide + mCurrentTask.getClass().getSimpleName()); return; } + Slog.d(TAG, "onEnumerated, remaining: " + remaining); ((EnumerateConsumer) mCurrentTask).onEnumerationResult(identifier, remaining); } diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java index 9d19fdf4868d..e3feb74248ec 100644 --- a/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/InternalEnumerateClient.java @@ -82,6 +82,7 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> private void handleEnumeratedTemplate(BiometricAuthenticator.Identifier identifier) { if (identifier == null) { + Slog.d(TAG, "Null identifier"); return; } Slog.v(TAG, "handleEnumeratedTemplate: " + identifier.getBiometricId()); @@ -103,6 +104,7 @@ public abstract class InternalEnumerateClient<T> extends HalClientMonitor<T> private void doTemplateCleanup() { if (mEnrolledList == null) { + Slog.d(TAG, "Null enrolledList"); return; } diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index ea759bf500dd..28dc5167487d 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -79,6 +79,7 @@ import android.os.UserHandle; import android.os.WorkSource; import android.os.WorkSource.WorkChain; import android.stats.location.LocationStatsEnums; +import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Log; @@ -87,6 +88,7 @@ import com.android.internal.util.DumpUtils; import com.android.internal.util.Preconditions; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.location.eventlog.LocationEventLog; import com.android.server.location.geofence.GeofenceManager; import com.android.server.location.geofence.GeofenceProxy; import com.android.server.location.gnss.GnssConfiguration; @@ -98,7 +100,6 @@ import com.android.server.location.injector.AppOpsHelper; import com.android.server.location.injector.EmergencyHelper; import com.android.server.location.injector.Injector; import com.android.server.location.injector.LocationAttributionHelper; -import com.android.server.location.injector.LocationEventLog; import com.android.server.location.injector.LocationPermissionsHelper; import com.android.server.location.injector.LocationPowerSaveModeHelper; import com.android.server.location.injector.LocationUsageLogger; @@ -147,9 +148,10 @@ public class LocationManagerService extends ILocationManager.Stub { public Lifecycle(Context context) { super(context); + LocationEventLog eventLog = new LocationEventLog(); mUserInfoHelper = new LifecycleUserInfoHelper(context); - mSystemInjector = new SystemInjector(context, mUserInfoHelper); - mService = new LocationManagerService(context, mSystemInjector); + mSystemInjector = new SystemInjector(context, mUserInfoHelper, eventLog); + mService = new LocationManagerService(context, mSystemInjector, eventLog); } @Override @@ -159,7 +161,7 @@ public class LocationManagerService extends ILocationManager.Stub { // client caching behavior is only enabled after seeing the first invalidate LocationManager.invalidateLocalLocationEnabledCaches(); // disable caching for our own process - Objects.requireNonNull(mService.mContext.getSystemService(LocationManager.class)) + Objects.requireNonNull(getContext().getSystemService(LocationManager.class)) .disableLocalLocationEnabledCaches(); } @@ -221,6 +223,7 @@ public class LocationManagerService extends ILocationManager.Stub { private final Context mContext; private final Injector mInjector; + private final LocationEventLog mEventLog; private final LocalService mLocalService; private final GeofenceManager mGeofenceManager; @@ -245,10 +248,10 @@ public class LocationManagerService extends ILocationManager.Stub { private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers = new CopyOnWriteArrayList<>(); - LocationManagerService(Context context, Injector injector) { + LocationManagerService(Context context, Injector injector, LocationEventLog eventLog) { mContext = context.createAttributionContext(ATTRIBUTION_TAG); mInjector = injector; - + mEventLog = eventLog; mLocalService = new LocalService(); LocalServices.addService(LocationManagerInternal.class, mLocalService); @@ -256,7 +259,7 @@ public class LocationManagerService extends ILocationManager.Stub { // set up passive provider first since it will be required for all other location providers, // which are loaded later once the system is ready. - mPassiveManager = new PassiveLocationProviderManager(mContext, injector); + mPassiveManager = new PassiveLocationProviderManager(mContext, injector, mEventLog); addLocationProviderManager(mPassiveManager, new PassiveLocationProvider(mContext)); // TODO: load the gps provider here as well, which will require refactoring @@ -297,7 +300,7 @@ public class LocationManagerService extends ILocationManager.Stub { } LocationProviderManager manager = new LocationProviderManager(mContext, mInjector, - providerName, mPassiveManager); + mEventLog, providerName, mPassiveManager); addLocationProviderManager(manager, null); return manager; } @@ -341,7 +344,7 @@ public class LocationManagerService extends ILocationManager.Stub { com.android.internal.R.string.config_networkLocationProviderPackageName); if (networkProvider != null) { LocationProviderManager networkManager = new LocationProviderManager(mContext, - mInjector, NETWORK_PROVIDER, mPassiveManager); + mInjector, mEventLog, NETWORK_PROVIDER, mPassiveManager); addLocationProviderManager(networkManager, networkProvider); } else { Log.w(TAG, "no network location provider found"); @@ -360,7 +363,7 @@ public class LocationManagerService extends ILocationManager.Stub { com.android.internal.R.string.config_fusedLocationProviderPackageName); if (fusedProvider != null) { LocationProviderManager fusedManager = new LocationProviderManager(mContext, mInjector, - FUSED_PROVIDER, mPassiveManager); + mEventLog, FUSED_PROVIDER, mPassiveManager); addLocationProviderManager(fusedManager, fusedProvider); } else { Log.wtf(TAG, "no fused location provider found"); @@ -375,7 +378,7 @@ public class LocationManagerService extends ILocationManager.Stub { mGnssManagerService.onSystemReady(); LocationProviderManager gnssManager = new LocationProviderManager(mContext, mInjector, - GPS_PROVIDER, mPassiveManager); + mEventLog, GPS_PROVIDER, mPassiveManager); addLocationProviderManager(gnssManager, mGnssManagerService.getGnssLocationProvider()); } @@ -431,7 +434,7 @@ public class LocationManagerService extends ILocationManager.Stub { Log.d(TAG, "[u" + userId + "] location enabled = " + enabled); } - mInjector.getLocationEventLog().logLocationEnabled(userId, enabled); + mEventLog.logLocationEnabled(userId, enabled); Intent intent = new Intent(LocationManager.MODE_CHANGED_ACTION) .putExtra(LocationManager.EXTRA_LOCATION_ENABLED, enabled) @@ -1193,9 +1196,27 @@ public class LocationManagerService extends ILocationManager.Stub { IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); - if (mGnssManagerService != null && args.length > 0 && args[0].equals("--gnssmetrics")) { - mGnssManagerService.dump(fd, ipw, args); - return; + if (args.length > 0) { + LocationProviderManager manager = getLocationProviderManager(args[0]); + if (manager != null) { + ipw.println("Provider:"); + ipw.increaseIndent(); + manager.dump(fd, ipw, args); + ipw.decreaseIndent(); + + ipw.println("Event Log:"); + ipw.increaseIndent(); + mEventLog.iterate(manager.getName(), ipw::println); + ipw.decreaseIndent(); + return; + } + + if ("--gnssmetrics".equals(args[0])) { + if (mGnssManagerService != null) { + mGnssManagerService.dump(fd, ipw, args); + } + return; + } } ipw.println("Location Manager State:"); @@ -1227,6 +1248,25 @@ public class LocationManagerService extends ILocationManager.Stub { } ipw.decreaseIndent(); + ipw.println("Historical Aggregate Location Provider Data:"); + ipw.increaseIndent(); + ArrayMap<String, ArrayMap<String, LocationEventLog.AggregateStats>> aggregateStats = + mEventLog.copyAggregateStats(); + for (int i = 0; i < aggregateStats.size(); i++) { + ipw.println(aggregateStats.keyAt(i)); + ipw.increaseIndent(); + ArrayMap<String, LocationEventLog.AggregateStats> providerStats = + aggregateStats.valueAt(i); + for (int j = 0; j < providerStats.size(); j++) { + ipw.print(providerStats.keyAt(j)); + ipw.print(": "); + providerStats.valueAt(j).updateTotals(); + ipw.println(providerStats.valueAt(j)); + } + ipw.decreaseIndent(); + } + ipw.decreaseIndent(); + if (mGnssManagerService != null) { ipw.println("GNSS Manager:"); ipw.increaseIndent(); @@ -1241,7 +1281,7 @@ public class LocationManagerService extends ILocationManager.Stub { ipw.println("Event Log:"); ipw.increaseIndent(); - mInjector.getLocationEventLog().iterate(ipw::println); + mEventLog.iterate(ipw::println); ipw.decreaseIndent(); } @@ -1320,7 +1360,6 @@ public class LocationManagerService extends ILocationManager.Stub { private final Context mContext; private final UserInfoHelper mUserInfoHelper; - private final LocationEventLog mLocationEventLog; private final AlarmHelper mAlarmHelper; private final SystemAppOpsHelper mAppOpsHelper; private final SystemLocationPermissionsHelper mLocationPermissionsHelper; @@ -1339,19 +1378,17 @@ public class LocationManagerService extends ILocationManager.Stub { @GuardedBy("this") private boolean mSystemReady; - SystemInjector(Context context, UserInfoHelper userInfoHelper) { + SystemInjector(Context context, UserInfoHelper userInfoHelper, LocationEventLog eventLog) { mContext = context; mUserInfoHelper = userInfoHelper; - mLocationEventLog = new LocationEventLog(); mAlarmHelper = new SystemAlarmHelper(context); mAppOpsHelper = new SystemAppOpsHelper(context); mLocationPermissionsHelper = new SystemLocationPermissionsHelper(context, mAppOpsHelper); mSettingsHelper = new SystemSettingsHelper(context); mAppForegroundHelper = new SystemAppForegroundHelper(context); - mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, - mLocationEventLog); + mLocationPowerSaveModeHelper = new SystemLocationPowerSaveModeHelper(context, eventLog); mScreenInteractiveHelper = new SystemScreenInteractiveHelper(context); mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper); mLocationUsageLogger = new LocationUsageLogger(); @@ -1430,11 +1467,6 @@ public class LocationManagerService extends ILocationManager.Stub { } @Override - public LocationEventLog getLocationEventLog() { - return mLocationEventLog; - } - - @Override public LocationUsageLogger getLocationUsageLogger() { return mLocationUsageLogger; } diff --git a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java index b5746bbf310a..12dd3e6a4a4c 100644 --- a/services/core/java/com/android/server/location/eventlog/LocalEventLog.java +++ b/services/core/java/com/android/server/location/eventlog/LocalEventLog.java @@ -16,12 +16,13 @@ package com.android.server.location.eventlog; +import android.annotation.Nullable; import android.os.SystemClock; import android.util.TimeUtils; import com.android.internal.util.Preconditions; -import java.util.ListIterator; +import java.util.Iterator; import java.util.NoSuchElementException; import java.util.function.Consumer; @@ -35,6 +36,7 @@ public abstract class LocalEventLog { boolean isFiller(); long getTimeDeltaMs(); String getLogString(); + boolean filter(@Nullable String filter); } private static final class FillerEvent implements Log { @@ -62,6 +64,11 @@ public abstract class LocalEventLog { public String getLogString() { throw new AssertionError(); } + + @Override + public boolean filter(String filter) { + return false; + } } /** @@ -87,6 +94,11 @@ public abstract class LocalEventLog { public final long getTimeDeltaMs() { return Integer.toUnsignedLong(mTimeDelta); } + + @Override + public boolean filter(String filter) { + return false; + } } // circular buffer of log entries @@ -198,6 +210,17 @@ public abstract class LocalEventLog { } } + /** + * Iterates over the event log, passing each filter-matching log string to the given + * consumer. + */ + public synchronized void iterate(String filter, Consumer<String> consumer) { + LogIterator it = new LogIterator(filter); + while (it.hasNext()) { + consumer.accept(it.next()); + } + } + // returns the index of the first element private int startIndex() { return wrapIndex(mLogEndIndex - mLogSize); @@ -205,12 +228,13 @@ public abstract class LocalEventLog { // returns the index after this one private int incrementIndex(int index) { - return wrapIndex(index + 1); - } - - // returns the index before this one - private int decrementIndex(int index) { - return wrapIndex(index - 1); + if (index == -1) { + return startIndex(); + } else if (index >= 0) { + return wrapIndex(index + 1); + } else { + throw new IllegalArgumentException(); + } } // rolls over the given index if necessary @@ -219,7 +243,9 @@ public abstract class LocalEventLog { return (index % mLog.length + mLog.length) % mLog.length; } - private class LogIterator implements ListIterator<String> { + private class LogIterator implements Iterator<String> { + + private final @Nullable String mFilter; private final long mSystemTimeDeltaMs; @@ -228,10 +254,17 @@ public abstract class LocalEventLog { private int mCount; LogIterator() { + this(null); + } + + LogIterator(@Nullable String filter) { + mFilter = filter; mSystemTimeDeltaMs = System.currentTimeMillis() - SystemClock.elapsedRealtime(); mCurrentRealtimeMs = mStartRealtimeMs; - mIndex = startIndex(); - mCount = 0; + mIndex = -1; + mCount = -1; + + increment(); } @Override @@ -239,75 +272,17 @@ public abstract class LocalEventLog { return mCount < mLogSize; } - @Override - public boolean hasPrevious() { - return mCount > 0; - } - - @Override - // return then increment public String next() { if (!hasNext()) { throw new NoSuchElementException(); } Log log = mLog[mIndex]; - long nextDeltaMs = log.getTimeDeltaMs(); - long realtimeMs = mCurrentRealtimeMs + nextDeltaMs; - - // calculate next index, skipping filler events - do { - mCurrentRealtimeMs += nextDeltaMs; - mIndex = incrementIndex(mIndex); - if (++mCount < mLogSize) { - nextDeltaMs = mLog[mIndex].getTimeDeltaMs(); - } - } while (mCount < mLogSize && mLog[mIndex].isFiller()); - - return getTimePrefix(realtimeMs + mSystemTimeDeltaMs) + log.getLogString(); - } - - @Override - // decrement then return - public String previous() { - Log log; - long currentDeltaMs; - long realtimeMs; - - // calculate previous index, skipping filler events with MAX_TIME_DELTA - do { - if (!hasPrevious()) { - throw new NoSuchElementException(); - } - - mIndex = decrementIndex(mIndex); - mCount--; - - log = mLog[mIndex]; - realtimeMs = mCurrentRealtimeMs; - - if (mCount > 0) { - currentDeltaMs = log.getTimeDeltaMs(); - mCurrentRealtimeMs -= currentDeltaMs; - } - } while (mCount >= 0 && log.isFiller()); - - return getTimePrefix(realtimeMs + mSystemTimeDeltaMs) + log.getLogString(); - } - - @Override - public int nextIndex() { - throw new UnsupportedOperationException(); - } + long timeMs = mCurrentRealtimeMs + log.getTimeDeltaMs() + mSystemTimeDeltaMs; - @Override - public int previousIndex() { - throw new UnsupportedOperationException(); - } + increment(); - @Override - public void add(String s) { - throw new UnsupportedOperationException(); + return getTimePrefix(timeMs) + log.getLogString(); } @Override @@ -315,9 +290,16 @@ public abstract class LocalEventLog { throw new UnsupportedOperationException(); } - @Override - public void set(String s) { - throw new UnsupportedOperationException(); + private void increment() { + long nextDeltaMs = mIndex == -1 ? 0 : mLog[mIndex].getTimeDeltaMs(); + do { + mCurrentRealtimeMs += nextDeltaMs; + mIndex = incrementIndex(mIndex); + if (++mCount < mLogSize) { + nextDeltaMs = mLog[mIndex].getTimeDeltaMs(); + } + } while (mCount < mLogSize && (mLog[mIndex].isFiller() || (mFilter != null + && !mLog[mIndex].filter(mFilter)))); } } } diff --git a/services/core/java/com/android/server/location/injector/LocationEventLog.java b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java index 8d73518bced1..67060fc2c082 100644 --- a/services/core/java/com/android/server/location/injector/LocationEventLog.java +++ b/services/core/java/com/android/server/location/eventlog/LocationEventLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2021 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. @@ -14,24 +14,32 @@ * limitations under the License. */ -package com.android.server.location.injector; +package com.android.server.location.eventlog; import static android.os.PowerManager.LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF; import static android.os.PowerManager.LOCATION_MODE_FOREGROUND_ONLY; import static android.os.PowerManager.LOCATION_MODE_GPS_DISABLED_WHEN_SCREEN_OFF; import static android.os.PowerManager.LOCATION_MODE_NO_CHANGE; import static android.os.PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF; +import static android.util.TimeUtils.formatDuration; import static com.android.server.location.LocationManagerService.D; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + import android.annotation.Nullable; import android.location.LocationRequest; import android.location.provider.ProviderRequest; import android.location.util.identity.CallerIdentity; import android.os.Build; import android.os.PowerManager.LocationPowerSaveMode; +import android.os.SystemClock; +import android.util.ArrayMap; -import com.android.server.location.eventlog.LocalEventLog; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; /** In memory event log for location events. */ public class LocationEventLog extends LocalEventLog { @@ -54,8 +62,39 @@ public class LocationEventLog extends LocalEventLog { private static final int EVENT_PROVIDER_DELIVER_LOCATION = 8; private static final int EVENT_LOCATION_POWER_SAVE_MODE_CHANGE = 9; + @GuardedBy("mAggregateStats") + private final ArrayMap<String, ArrayMap<String, AggregateStats>> mAggregateStats; + public LocationEventLog() { super(getLogSize()); + mAggregateStats = new ArrayMap<>(4); + } + + public ArrayMap<String, ArrayMap<String, AggregateStats>> copyAggregateStats() { + synchronized (mAggregateStats) { + ArrayMap<String, ArrayMap<String, AggregateStats>> copy = new ArrayMap<>( + mAggregateStats); + for (int i = 0; i < copy.size(); i++) { + copy.setValueAt(i, new ArrayMap<>(copy.valueAt(i))); + } + return copy; + } + } + + private AggregateStats getAggregateStats(String provider, String packageName) { + synchronized (mAggregateStats) { + ArrayMap<String, AggregateStats> packageMap = mAggregateStats.get(provider); + if (packageMap == null) { + packageMap = new ArrayMap<>(2); + mAggregateStats.put(provider, packageMap); + } + AggregateStats stats = packageMap.get(packageName); + if (stats == null) { + stats = new AggregateStats(); + packageMap.put(packageName, stats); + } + return stats; + } } /** Logs a location enabled/disabled event. */ @@ -77,12 +116,34 @@ public class LocationEventLog extends LocalEventLog { public void logProviderClientRegistered(String provider, CallerIdentity identity, LocationRequest request) { addLogEvent(EVENT_PROVIDER_REGISTER_CLIENT, provider, identity, request); + getAggregateStats(provider, identity.getPackageName()) + .markRequestAdded(request.getIntervalMillis()); } /** Logs a client unregistration for a location provider. */ - public void logProviderClientUnregistered(String provider, - CallerIdentity identity) { + public void logProviderClientUnregistered(String provider, CallerIdentity identity) { addLogEvent(EVENT_PROVIDER_UNREGISTER_CLIENT, provider, identity); + getAggregateStats(provider, identity.getPackageName()).markRequestRemoved(); + } + + /** Logs a client for a location provider entering the active state. */ + public void logProviderClientActive(String provider, CallerIdentity identity) { + getAggregateStats(provider, identity.getPackageName()).markRequestActive(); + } + + /** Logs a client for a location provider leaving the active state. */ + public void logProviderClientInactive(String provider, CallerIdentity identity) { + getAggregateStats(provider, identity.getPackageName()).markRequestInactive(); + } + + /** Logs a client for a location provider entering the foreground state. */ + public void logProviderClientForeground(String provider, CallerIdentity identity) { + getAggregateStats(provider, identity.getPackageName()).markRequestForeground(); + } + + /** Logs a client for a location provider leaving the foreground state. */ + public void logProviderClientBackground(String provider, CallerIdentity identity) { + getAggregateStats(provider, identity.getPackageName()).markRequestBackground(); } /** Logs a change to the provider request for a location provider. */ @@ -143,16 +204,29 @@ public class LocationEventLog extends LocalEventLog { } } - private static class ProviderEnabledEvent extends LogEvent { + private abstract static class ProviderEvent extends LogEvent { + + protected final String mProvider; + + protected ProviderEvent(long timeDelta, String provider) { + super(timeDelta); + mProvider = provider; + } + + @Override + public boolean filter(String filter) { + return mProvider.equals(filter); + } + } + + private static final class ProviderEnabledEvent extends ProviderEvent { - private final String mProvider; private final int mUserId; private final boolean mEnabled; protected ProviderEnabledEvent(long timeDelta, String provider, int userId, boolean enabled) { - super(timeDelta); - mProvider = provider; + super(timeDelta, provider); mUserId = userId; mEnabled = enabled; } @@ -164,14 +238,12 @@ public class LocationEventLog extends LocalEventLog { } } - private static class ProviderMockedEvent extends LogEvent { + private static final class ProviderMockedEvent extends ProviderEvent { - private final String mProvider; private final boolean mMocked; protected ProviderMockedEvent(long timeDelta, String provider, boolean mocked) { - super(timeDelta); - mProvider = provider; + super(timeDelta, provider); mMocked = mocked; } @@ -185,17 +257,15 @@ public class LocationEventLog extends LocalEventLog { } } - private static class ProviderRegisterEvent extends LogEvent { + private static final class ProviderRegisterEvent extends ProviderEvent { - private final String mProvider; private final boolean mRegistered; private final CallerIdentity mIdentity; @Nullable private final LocationRequest mLocationRequest; private ProviderRegisterEvent(long timeDelta, String provider, boolean registered, CallerIdentity identity, @Nullable LocationRequest locationRequest) { - super(timeDelta); - mProvider = provider; + super(timeDelta, provider); mRegistered = registered; mIdentity = identity; mLocationRequest = locationRequest; @@ -212,14 +282,12 @@ public class LocationEventLog extends LocalEventLog { } } - private static class ProviderUpdateEvent extends LogEvent { + private static final class ProviderUpdateEvent extends ProviderEvent { - private final String mProvider; private final ProviderRequest mRequest; private ProviderUpdateEvent(long timeDelta, String provider, ProviderRequest request) { - super(timeDelta); - mProvider = provider; + super(timeDelta, provider); mRequest = request; } @@ -229,14 +297,12 @@ public class LocationEventLog extends LocalEventLog { } } - private static class ProviderReceiveLocationEvent extends LogEvent { + private static final class ProviderReceiveLocationEvent extends ProviderEvent { - private final String mProvider; private final int mNumLocations; private ProviderReceiveLocationEvent(long timeDelta, String provider, int numLocations) { - super(timeDelta); - mProvider = provider; + super(timeDelta, provider); mNumLocations = numLocations; } @@ -246,16 +312,14 @@ public class LocationEventLog extends LocalEventLog { } } - private static class ProviderDeliverLocationEvent extends LogEvent { + private static final class ProviderDeliverLocationEvent extends ProviderEvent { - private final String mProvider; private final int mNumLocations; @Nullable private final CallerIdentity mIdentity; private ProviderDeliverLocationEvent(long timeDelta, String provider, int numLocations, @Nullable CallerIdentity identity) { - super(timeDelta); - mProvider = provider; + super(timeDelta, provider); mNumLocations = numLocations; mIdentity = identity; } @@ -267,7 +331,7 @@ public class LocationEventLog extends LocalEventLog { } } - private static class LocationPowerSaveModeEvent extends LogEvent { + private static final class LocationPowerSaveModeEvent extends LogEvent { @LocationPowerSaveMode private final int mLocationPowerSaveMode; @@ -305,7 +369,7 @@ public class LocationEventLog extends LocalEventLog { } } - private static class LocationEnabledEvent extends LogEvent { + private static final class LocationEnabledEvent extends LogEvent { private final int mUserId; private final boolean mEnabled; @@ -321,4 +385,118 @@ public class LocationEventLog extends LocalEventLog { return "[u" + mUserId + "] location setting " + (mEnabled ? "enabled" : "disabled"); } } + + /** + * Aggregate statistics for a single package under a single provider. + */ + public static final class AggregateStats { + + @GuardedBy("this") + private int mAddedRequestCount; + @GuardedBy("this") + private int mActiveRequestCount; + @GuardedBy("this") + private int mForegroundRequestCount; + + @GuardedBy("this") + private long mFastestIntervalMs = Long.MAX_VALUE; + @GuardedBy("this") + private long mSlowestIntervalMs = 0; + + @GuardedBy("this") + private long mAddedTimeTotalMs; + @GuardedBy("this") + private long mAddedTimeLastUpdateRealtimeMs; + + @GuardedBy("this") + private long mActiveTimeTotalMs; + @GuardedBy("this") + private long mActiveTimeLastUpdateRealtimeMs; + + @GuardedBy("this") + private long mForegroundTimeTotalMs; + @GuardedBy("this") + private long mForegroundTimeLastUpdateRealtimeMs; + + AggregateStats() {} + + synchronized void markRequestAdded(long intervalMillis) { + if (mAddedRequestCount++ == 0) { + mAddedTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime(); + } + + mFastestIntervalMs = min(intervalMillis, mFastestIntervalMs); + mSlowestIntervalMs = max(intervalMillis, mSlowestIntervalMs); + } + + synchronized void markRequestRemoved() { + updateTotals(); + --mAddedRequestCount; + Preconditions.checkState(mAddedRequestCount >= 0); + + mActiveRequestCount = min(mAddedRequestCount, mActiveRequestCount); + mForegroundRequestCount = min(mAddedRequestCount, mForegroundRequestCount); + } + + synchronized void markRequestActive() { + Preconditions.checkState(mAddedRequestCount > 0); + if (mActiveRequestCount++ == 0) { + mActiveTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime(); + } + } + + synchronized void markRequestInactive() { + updateTotals(); + --mActiveRequestCount; + Preconditions.checkState(mActiveRequestCount >= 0); + } + + synchronized void markRequestForeground() { + Preconditions.checkState(mAddedRequestCount > 0); + if (mForegroundRequestCount++ == 0) { + mForegroundTimeLastUpdateRealtimeMs = SystemClock.elapsedRealtime(); + } + } + + synchronized void markRequestBackground() { + updateTotals(); + --mForegroundRequestCount; + Preconditions.checkState(mForegroundRequestCount >= 0); + } + + public synchronized void updateTotals() { + if (mAddedRequestCount > 0) { + long realtimeMs = SystemClock.elapsedRealtime(); + mAddedTimeTotalMs += realtimeMs - mAddedTimeLastUpdateRealtimeMs; + mAddedTimeLastUpdateRealtimeMs = realtimeMs; + } + if (mActiveRequestCount > 0) { + long realtimeMs = SystemClock.elapsedRealtime(); + mActiveTimeTotalMs += realtimeMs - mActiveTimeLastUpdateRealtimeMs; + mActiveTimeLastUpdateRealtimeMs = realtimeMs; + } + if (mForegroundRequestCount > 0) { + long realtimeMs = SystemClock.elapsedRealtime(); + mForegroundTimeTotalMs += realtimeMs - mForegroundTimeLastUpdateRealtimeMs; + mForegroundTimeLastUpdateRealtimeMs = realtimeMs; + } + } + + @Override + public synchronized String toString() { + return "min/max interval = " + intervalToString(mFastestIntervalMs) + "/" + + intervalToString(mSlowestIntervalMs) + + ", total/active/foreground duration = " + formatDuration(mAddedTimeTotalMs) + + "/" + formatDuration(mActiveTimeTotalMs) + "/" + + formatDuration(mForegroundTimeTotalMs); + } + + private static String intervalToString(long intervalMs) { + if (intervalMs == LocationRequest.PASSIVE_INTERVAL) { + return "passive"; + } else { + return MILLISECONDS.toSeconds(intervalMs) + "s"; + } + } + } } diff --git a/services/core/java/com/android/server/location/injector/Injector.java b/services/core/java/com/android/server/location/injector/Injector.java index 03938b2b8ba2..0e157c22a32b 100644 --- a/services/core/java/com/android/server/location/injector/Injector.java +++ b/services/core/java/com/android/server/location/injector/Injector.java @@ -56,7 +56,4 @@ public interface Injector { /** Returns a LocationUsageLogger. */ LocationUsageLogger getLocationUsageLogger(); - - /** Returns a LocationEventLog. */ - LocationEventLog getLocationEventLog(); } diff --git a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java index 532826a02ab0..cc00d5684991 100644 --- a/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java +++ b/services/core/java/com/android/server/location/injector/LocationPowerSaveModeHelper.java @@ -24,6 +24,8 @@ import static com.android.server.location.LocationManagerService.TAG; import android.os.PowerManager.LocationPowerSaveMode; import android.util.Log; +import com.android.server.location.eventlog.LocationEventLog; + import java.util.concurrent.CopyOnWriteArrayList; /** diff --git a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java index 1b74865b268e..c47a64d6d9a7 100644 --- a/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java +++ b/services/core/java/com/android/server/location/injector/SystemLocationPowerSaveModeHelper.java @@ -25,6 +25,7 @@ import android.os.PowerSaveState; import com.android.internal.util.Preconditions; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.location.eventlog.LocationEventLog; import java.util.Objects; import java.util.function.Consumer; diff --git a/services/core/java/com/android/server/location/provider/LocationProviderManager.java b/services/core/java/com/android/server/location/provider/LocationProviderManager.java index 48a012e57a02..388b5a4dd54e 100644 --- a/services/core/java/com/android/server/location/provider/LocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/LocationProviderManager.java @@ -89,6 +89,7 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.location.LocationPermissions; import com.android.server.location.LocationPermissions.PermissionLevel; +import com.android.server.location.eventlog.LocationEventLog; import com.android.server.location.fudger.LocationFudger; import com.android.server.location.injector.AlarmHelper; import com.android.server.location.injector.AppForegroundHelper; @@ -96,7 +97,6 @@ import com.android.server.location.injector.AppForegroundHelper.AppForegroundLis import com.android.server.location.injector.AppOpsHelper; import com.android.server.location.injector.Injector; import com.android.server.location.injector.LocationAttributionHelper; -import com.android.server.location.injector.LocationEventLog; import com.android.server.location.injector.LocationPermissionsHelper; import com.android.server.location.injector.LocationPermissionsHelper.LocationPermissionsListener; import com.android.server.location.injector.LocationPowerSaveModeHelper; @@ -323,7 +323,7 @@ public class LocationProviderManager extends + getRequest()); } - mLocationEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest()); + mEventLog.logProviderClientRegistered(mName, getIdentity(), super.getRequest()); // initialization order is important as there are ordering dependencies mPermitted = mLocationPermissionsHelper.hasLocationPermissions(mPermissionLevel, @@ -333,6 +333,10 @@ public class LocationProviderManager extends mIsUsingHighPower = isUsingHighPower(); onProviderListenerRegister(); + + if (mForeground) { + mEventLog.logProviderClientForeground(mName, getIdentity()); + } } @GuardedBy("mLock") @@ -344,7 +348,7 @@ public class LocationProviderManager extends onProviderListenerUnregister(); - mLocationEventLog.logProviderClientUnregistered(mName, getIdentity()); + mEventLog.logProviderClientUnregistered(mName, getIdentity()); if (D) { Log.d(TAG, mName + " provider removed registration from " + getIdentity()); @@ -369,6 +373,8 @@ public class LocationProviderManager extends Preconditions.checkState(Thread.holdsLock(mLock)); } + mEventLog.logProviderClientActive(mName, getIdentity()); + if (!getRequest().isHiddenFromAppOps()) { mLocationAttributionHelper.reportLocationStart(getIdentity(), getName(), getKey()); } @@ -389,6 +395,8 @@ public class LocationProviderManager extends } onProviderListenerInactive(); + + mEventLog.logProviderClientInactive(mName, getIdentity()); } /** @@ -524,6 +532,12 @@ public class LocationProviderManager extends mForeground = foreground; + if (mForeground) { + mEventLog.logProviderClientForeground(mName, getIdentity()); + } else { + mEventLog.logProviderClientBackground(mName, getIdentity()); + } + // note that onProviderLocationRequestChanged() is always called return onProviderLocationRequestChanged() || mLocationPowerSaveModeHelper.getLocationPowerSaveMode() @@ -855,7 +869,7 @@ public class LocationProviderManager extends listener.deliverOnLocationChanged(deliverLocationResult, mUseWakeLock ? mWakeLock::release : null); - mLocationEventLog.logProviderDeliveredLocations(mName, locationResult.size(), + mEventLog.logProviderDeliveredLocations(mName, locationResult.size(), getIdentity()); } @@ -1154,7 +1168,7 @@ public class LocationProviderManager extends // we currently don't hold a wakelock for getCurrentLocation deliveries listener.deliverOnLocationChanged(deliverLocationResult, null); - mLocationEventLog.logProviderDeliveredLocations(mName, + mEventLog.logProviderDeliveredLocations(mName, locationResult != null ? locationResult.size() : 0, getIdentity()); } @@ -1223,6 +1237,7 @@ public class LocationProviderManager extends private final CopyOnWriteArrayList<IProviderRequestListener> mProviderRequestListeners; + protected final LocationEventLog mEventLog; protected final LocationManagerInternal mLocationManagerInternal; protected final SettingsHelper mSettingsHelper; protected final UserInfoHelper mUserHelper; @@ -1235,7 +1250,6 @@ public class LocationProviderManager extends protected final LocationAttributionHelper mLocationAttributionHelper; protected final LocationUsageLogger mLocationUsageLogger; protected final LocationFudger mLocationFudger; - protected final LocationEventLog mLocationEventLog; private final UserListener mUserChangedListener = this::onUserChanged; private final UserSettingChangedListener mLocationEnabledChangedListener = @@ -1273,8 +1287,8 @@ public class LocationProviderManager extends @GuardedBy("mLock") private @Nullable OnAlarmListener mDelayedRegister; - public LocationProviderManager(Context context, Injector injector, String name, - @Nullable PassiveLocationProviderManager passiveManager) { + public LocationProviderManager(Context context, Injector injector, LocationEventLog eventLog, + String name, @Nullable PassiveLocationProviderManager passiveManager) { mContext = context; mName = Objects.requireNonNull(name); mPassiveManager = passiveManager; @@ -1285,6 +1299,7 @@ public class LocationProviderManager extends mEnabledListeners = new ArrayList<>(); mProviderRequestListeners = new CopyOnWriteArrayList<>(); + mEventLog = eventLog; mLocationManagerInternal = Objects.requireNonNull( LocalServices.getService(LocationManagerInternal.class)); mSettingsHelper = injector.getSettingsHelper(); @@ -1297,7 +1312,6 @@ public class LocationProviderManager extends mScreenInteractiveHelper = injector.getScreenInteractiveHelper(); mLocationAttributionHelper = injector.getLocationAttributionHelper(); mLocationUsageLogger = injector.getLocationUsageLogger(); - mLocationEventLog = injector.getLocationEventLog(); mLocationFudger = new LocationFudger(mSettingsHelper.getCoarseLocationAccuracyM()); mProvider = new MockableLocationProvider(mLock); @@ -1437,7 +1451,7 @@ public class LocationProviderManager extends synchronized (mLock) { Preconditions.checkState(mState != STATE_STOPPED); - mLocationEventLog.logProviderMocked(mName, provider != null); + mEventLog.logProviderMocked(mName, provider != null); final long identity = Binder.clearCallingIdentity(); try { @@ -1925,7 +1939,7 @@ public class LocationProviderManager extends @GuardedBy("mLock") private void setProviderRequest(ProviderRequest request) { - mLocationEventLog.logProviderUpdateRequest(mName, request); + mEventLog.logProviderUpdateRequest(mName, request); mProvider.getController().setRequest(request); FgThread.getHandler().post(() -> { @@ -2261,7 +2275,7 @@ public class LocationProviderManager extends } // don't log location received for passive provider because it's spammy - mLocationEventLog.logProviderReceivedLocations(mName, filtered.size()); + mEventLog.logProviderReceivedLocations(mName, filtered.size()); } else { // passive provider should get already filtered results as input filtered = locationResult; @@ -2361,7 +2375,7 @@ public class LocationProviderManager extends if (D) { Log.d(TAG, "[u" + userId + "] " + mName + " provider enabled = " + enabled); } - mLocationEventLog.logProviderEnabled(mName, userId, enabled); + mEventLog.logProviderEnabled(mName, userId, enabled); } // clear last locations if we become disabled diff --git a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java index b35af4f6475c..027f4e94f55b 100644 --- a/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java +++ b/services/core/java/com/android/server/location/provider/PassiveLocationProviderManager.java @@ -24,6 +24,7 @@ import android.location.provider.ProviderRequest; import android.os.Binder; import com.android.internal.util.Preconditions; +import com.android.server.location.eventlog.LocationEventLog; import com.android.server.location.injector.Injector; import java.util.Collection; @@ -33,8 +34,9 @@ import java.util.Collection; */ public class PassiveLocationProviderManager extends LocationProviderManager { - public PassiveLocationProviderManager(Context context, Injector injector) { - super(context, injector, LocationManager.PASSIVE_PROVIDER, null); + public PassiveLocationProviderManager(Context context, Injector injector, + LocationEventLog eventLog) { + super(context, injector, eventLog, LocationManager.PASSIVE_PROVIDER, null); } @Override diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index f7f1865f757e..4c3dfbff3f87 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -378,7 +378,9 @@ public class NotificationManagerService extends SystemService { static final String[] DEFAULT_ALLOWED_ADJUSTMENTS = new String[] { Adjustment.KEY_CONTEXTUAL_ACTIONS, Adjustment.KEY_TEXT_REPLIES, - Adjustment.KEY_NOT_CONVERSATION}; + Adjustment.KEY_NOT_CONVERSATION, + Adjustment.KEY_IMPORTANCE, + Adjustment.KEY_RANKING_SCORE}; static final String[] NON_BLOCKABLE_DEFAULT_ROLES = new String[] { RoleManager.ROLE_DIALER, @@ -9048,7 +9050,8 @@ public class NotificationManagerService extends SystemService { public class NotificationAssistants extends ManagedServices { static final String TAG_ENABLED_NOTIFICATION_ASSISTANTS = "enabled_assistants"; - private static final String TAG_ALLOWED_ADJUSTMENT_TYPES = "q_allowed_adjustments"; + private static final String TAG_ALLOWED_ADJUSTMENT_TYPES_OLD = "q_allowed_adjustments"; + private static final String TAG_ALLOWED_ADJUSTMENT_TYPES = "s_allowed_adjustments"; private static final String ATT_TYPES = "types"; private final Object mLock = new Object(); @@ -9150,13 +9153,19 @@ public class NotificationManagerService extends SystemService { @Override protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException { - if (TAG_ALLOWED_ADJUSTMENT_TYPES.equals(tag)) { + if (TAG_ALLOWED_ADJUSTMENT_TYPES_OLD.equals(tag) + || TAG_ALLOWED_ADJUSTMENT_TYPES.equals(tag)) { final String types = XmlUtils.readStringAttribute(parser, ATT_TYPES); synchronized (mLock) { mAllowedAdjustments.clear(); if (!TextUtils.isEmpty(types)) { mAllowedAdjustments.addAll(Arrays.asList(types.split(","))); } + if (TAG_ALLOWED_ADJUSTMENT_TYPES_OLD.equals(tag)) { + if (DEBUG) Slog.d(TAG, "Migrate allowed adjustments."); + mAllowedAdjustments.addAll( + Arrays.asList(DEFAULT_ALLOWED_ADJUSTMENTS)); + } } } } diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 50fb176e3a5a..fd2fb1fcab39 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -50,6 +50,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; +import android.content.pm.overlay.OverlayPaths; import android.content.res.ApkAssets; import android.net.Uri; import android.os.Binder; @@ -1376,18 +1377,18 @@ public final class OverlayManagerService extends SystemService { targetPackageNames = pm.getTargetPackageNames(userId); } - final Map<String, List<String>> pendingChanges = + final Map<String, OverlayPaths> pendingChanges = new ArrayMap<>(targetPackageNames.size()); synchronized (mLock) { - final List<String> frameworkOverlays = - mImpl.getEnabledOverlayPackageNames("android", userId); + final OverlayPaths frameworkOverlays = + mImpl.getEnabledOverlayPaths("android", userId); for (final String targetPackageName : targetPackageNames) { - List<String> list = new ArrayList<>(); + final OverlayPaths.Builder list = new OverlayPaths.Builder(); if (!"android".equals(targetPackageName)) { list.addAll(frameworkOverlays); } - list.addAll(mImpl.getEnabledOverlayPackageNames(targetPackageName, userId)); - pendingChanges.put(targetPackageName, list); + list.addAll(mImpl.getEnabledOverlayPaths(targetPackageName, userId)); + pendingChanges.put(targetPackageName, list.build()); } } @@ -1395,7 +1396,7 @@ public final class OverlayManagerService extends SystemService { for (final String targetPackageName : targetPackageNames) { if (DEBUG) { Slog.d(TAG, "-> Updating overlay: target=" + targetPackageName + " overlays=[" - + TextUtils.join(",", pendingChanges.get(targetPackageName)) + + pendingChanges.get(targetPackageName) + "] userId=" + userId); } diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java index e60411bb78c5..c547c36a8033 100644 --- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java +++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java @@ -31,6 +31,7 @@ import android.annotation.Nullable; import android.content.om.OverlayInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; +import android.content.pm.overlay.OverlayPaths; import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; @@ -697,19 +698,20 @@ final class OverlayManagerServiceImpl { removeIdmapIfPossible(oi); } - List<String> getEnabledOverlayPackageNames(@NonNull final String targetPackageName, + OverlayPaths getEnabledOverlayPaths(@NonNull final String targetPackageName, final int userId) { final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackageName, userId); - final List<String> paths = new ArrayList<>(overlays.size()); + final OverlayPaths.Builder paths = new OverlayPaths.Builder(); final int n = overlays.size(); for (int i = 0; i < n; i++) { final OverlayInfo oi = overlays.get(i); - if (oi.isEnabled()) { - paths.add(oi.packageName); + if (!oi.isEnabled()) { + continue; } + paths.addApkPath(oi.baseCodePath); } - return paths; + return paths.build(); } /** diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java index 15e1d5281bfa..7bf704299373 100644 --- a/services/core/java/com/android/server/pm/InstantAppRegistry.java +++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java @@ -59,6 +59,7 @@ import com.android.server.pm.permission.PermissionManagerServiceInternal; import com.android.server.utils.Snappable; import com.android.server.utils.Watchable; import com.android.server.utils.WatchableImpl; +import com.android.server.utils.Watched; import com.android.server.utils.WatchedSparseArray; import com.android.server.utils.WatchedSparseBooleanArray; import com.android.server.utils.Watcher; @@ -123,6 +124,7 @@ class InstantAppRegistry implements Watchable, Snappable { private final CookiePersistence mCookiePersistence; /** State for uninstalled instant apps */ + @Watched @GuardedBy("mService.mLock") private final WatchedSparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps; @@ -132,10 +134,12 @@ class InstantAppRegistry implements Watchable, Snappable { * The value is a set of instant app UIDs. * UserID -> TargetAppId -> InstantAppId */ + @Watched @GuardedBy("mService.mLock") private final WatchedSparseArray<WatchedSparseArray<WatchedSparseBooleanArray>> mInstantGrants; /** The set of all installed instant apps. UserID -> AppID */ + @Watched @GuardedBy("mService.mLock") private final WatchedSparseArray<WatchedSparseBooleanArray> mInstalledInstantAppUids; @@ -189,6 +193,7 @@ class InstantAppRegistry implements Watchable, Snappable { mUninstalledInstantApps.registerObserver(mObserver); mInstantGrants.registerObserver(mObserver); mInstalledInstantAppUids.registerObserver(mObserver); + Watchable.verifyWatchedAttributes(this, mObserver); } /** diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 965f68bdcaf3..0c143c97e1aa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -193,6 +193,7 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.IntentFilterVerificationInfo; import android.content.pm.KeySet; import android.content.pm.ModuleInfo; +import android.content.pm.overlay.OverlayPaths; import android.content.pm.PackageChangeEvent; import android.content.pm.PackageInfo; import android.content.pm.PackageInfoLite; @@ -234,6 +235,7 @@ import android.content.pm.VersionedPackage; import android.content.pm.dex.ArtManager; import android.content.pm.dex.DexMetadataHelper; import android.content.pm.dex.IArtManager; +import android.content.pm.overlay.OverlayPaths; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.ParsingPackageUtils; @@ -1747,6 +1749,11 @@ public class PackageManagerService extends IPackageManager.Stub public AndroidPackage getPackage(@NonNull String packageName) { return getPackageLocked(packageName); } + + @Override + public boolean filterAppAccess(String packageName, int callingUid, int userId) { + return mPmInternal.filterAppAccess(packageName, callingUid, userId); + } } /** @@ -5938,6 +5945,21 @@ public class PackageManagerService extends IPackageManager.Stub } } + // Link watchables to the class + private void registerObserver() { + mPackages.registerObserver(mWatcher); + mSharedLibraries.registerObserver(mWatcher); + mStaticLibsByDeclaringPackage.registerObserver(mWatcher); + mInstrumentation.registerObserver(mWatcher); + mWebInstantAppsDisabled.registerObserver(mWatcher); + mAppsFilter.registerObserver(mWatcher); + mInstantAppRegistry.registerObserver(mWatcher); + mSettings.registerObserver(mWatcher); + // If neither "build" attribute is true then this may be a mockito test, and verification + // can fail as a false positive. + Watchable.verifyWatchedAttributes(this, mWatcher, !(mIsEngBuild || mIsUserDebugBuild)); + } + /** * A extremely minimal constructor designed to start up a PackageManagerService instance for * testing. @@ -6021,15 +6043,7 @@ public class PackageManagerService extends IPackageManager.Stub sSnapshotCorked = true; mLiveComputer = createLiveComputer(); mSnapshotComputer = mLiveComputer; - - // Link up the watchers - mPackages.registerObserver(mWatcher); - mSharedLibraries.registerObserver(mWatcher); - mStaticLibsByDeclaringPackage.registerObserver(mWatcher); - mInstrumentation.registerObserver(mWatcher); - mWebInstantAppsDisabled.registerObserver(mWatcher); - mAppsFilter.registerObserver(mWatcher); - Watchable.verifyWatchedAttributes(this, mWatcher); + registerObserver(); mPackages.putAll(testParams.packages); mEnableFreeCacheV2 = testParams.enableFreeCacheV2; @@ -6183,15 +6197,6 @@ public class PackageManagerService extends IPackageManager.Stub mDomainVerificationManager = injector.getDomainVerificationManagerInternal(); mDomainVerificationManager.setConnection(mDomainVerificationConnection); - // Link up the watchers - mPackages.registerObserver(mWatcher); - mSharedLibraries.registerObserver(mWatcher); - mStaticLibsByDeclaringPackage.registerObserver(mWatcher); - mInstrumentation.registerObserver(mWatcher); - mWebInstantAppsDisabled.registerObserver(mWatcher); - mAppsFilter.registerObserver(mWatcher); - Watchable.verifyWatchedAttributes(this, mWatcher); - // Create the computer as soon as the state objects have been installed. The // cached computer is the same as the live computer until the end of the // constructor, at which time the invalidation method updates it. The cache is @@ -6200,6 +6205,7 @@ public class PackageManagerService extends IPackageManager.Stub sSnapshotCorked = true; mLiveComputer = createLiveComputer(); mSnapshotComputer = mLiveComputer; + registerObserver(); // CHECKSTYLE:OFF IndentationCheck synchronized (mInstallLock) { @@ -16156,8 +16162,7 @@ public class PackageManagerService extends IPackageManager.Stub @Deprecated @Override public boolean updateIntentVerificationStatus(String packageName, int status, int userId) { - mDomainVerificationManager.setLegacyUserState(packageName, userId, status); - return true; + return mDomainVerificationManager.setLegacyUserState(packageName, userId, status); } @Deprecated @@ -18119,11 +18124,8 @@ public class PackageManagerService extends IPackageManager.Stub if (libPs == null) { continue; } - final String[] overlayPaths = libPs.getOverlayPaths(currentUserId); - if (overlayPaths != null) { - ps.setOverlayPathsForLibrary(sharedLib.getName(), - Arrays.asList(overlayPaths), currentUserId); - } + ps.setOverlayPathsForLibrary(sharedLib.getName(), + libPs.getOverlayPaths(currentUserId), currentUserId); } } } @@ -26614,34 +26616,19 @@ public class PackageManagerService extends IPackageManager.Stub @Override public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName, - @Nullable List<String> overlayPackageNames, - @NonNull Collection<String> outUpdatedPackageNames) { + @Nullable OverlayPaths overlayPaths, + @NonNull Set<String> outUpdatedPackageNames) { + boolean modified = false; synchronized (mLock) { final AndroidPackage targetPkg = mPackages.get(targetPackageName); if (targetPackageName == null || targetPkg == null) { Slog.e(TAG, "failed to find package " + targetPackageName); return false; } - ArrayList<String> overlayPaths = null; - if (overlayPackageNames != null && overlayPackageNames.size() > 0) { - final int N = overlayPackageNames.size(); - overlayPaths = new ArrayList<>(N); - for (int i = 0; i < N; i++) { - final String packageName = overlayPackageNames.get(i); - final AndroidPackage pkg = mPackages.get(packageName); - if (pkg == null) { - Slog.e(TAG, "failed to find package " + packageName); - return false; - } - overlayPaths.add(pkg.getBaseApkPath()); - } - } - ArraySet<String> updatedPackageNames = null; if (targetPkg.getLibraryNames() != null) { // Set the overlay paths for dependencies of the shared library. - updatedPackageNames = new ArraySet<>(); - for (String libName : targetPkg.getLibraryNames()) { + for (final String libName : targetPkg.getLibraryNames()) { final SharedLibraryInfo info = getSharedLibraryInfoLPr(libName, SharedLibraryInfo.VERSION_UNDEFINED); if (info == null) { @@ -26652,28 +26639,30 @@ public class PackageManagerService extends IPackageManager.Stub if (dependents == null) { continue; } - for (VersionedPackage dependent : dependents) { + for (final VersionedPackage dependent : dependents) { final PackageSetting ps = mSettings.getPackageLPr( dependent.getPackageName()); if (ps == null) { continue; } - ps.setOverlayPathsForLibrary(libName, overlayPaths, userId); - updatedPackageNames.add(dependent.getPackageName()); + if (ps.setOverlayPathsForLibrary(libName, overlayPaths, userId)) { + outUpdatedPackageNames.add(dependent.getPackageName()); + modified = true; + } } } } final PackageSetting ps = mSettings.getPackageLPr(targetPackageName); - ps.setOverlayPaths(overlayPaths, userId); - - outUpdatedPackageNames.add(targetPackageName); - if (updatedPackageNames != null) { - outUpdatedPackageNames.addAll(updatedPackageNames); + if (ps.setOverlayPaths(overlayPaths, userId)) { + outUpdatedPackageNames.add(targetPackageName); + modified = true; } } - invalidatePackageInfoCache(); + if (modified) { + invalidatePackageInfoCache(); + } return true; } diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index 3a142837e063..5364cbfede86 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -31,6 +31,7 @@ import android.content.pm.PackageParser; import android.content.pm.PackageUserState; import android.content.pm.Signature; import android.content.pm.SuspendDialogInfo; +import android.content.pm.overlay.OverlayPaths; import android.os.PersistableBundle; import android.os.incremental.IncrementalManager; import android.service.pm.PackageProto; @@ -44,7 +45,6 @@ import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.File; import java.util.Arrays; -import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -327,21 +327,20 @@ public abstract class PackageSettingBase extends SettingBase { modifyUserState(userId).uninstallReason = uninstallReason; } - void setOverlayPaths(List<String> overlayPaths, int userId) { - modifyUserState(userId).setOverlayPaths(overlayPaths == null ? null : - overlayPaths.toArray(new String[overlayPaths.size()])); + boolean setOverlayPaths(OverlayPaths overlayPaths, int userId) { + return modifyUserState(userId).setOverlayPaths(overlayPaths); } - String[] getOverlayPaths(int userId) { + OverlayPaths getOverlayPaths(int userId) { return readUserState(userId).getOverlayPaths(); } - void setOverlayPathsForLibrary(String libName, List<String> overlayPaths, int userId) { - modifyUserState(userId).setSharedLibraryOverlayPaths(libName, - overlayPaths == null ? null : overlayPaths.toArray(new String[0])); + boolean setOverlayPathsForLibrary(String libName, OverlayPaths overlayPaths, + int userId) { + return modifyUserState(userId).setSharedLibraryOverlayPaths(libName, overlayPaths); } - Map<String, String[]> getOverlayPathsForLibrary(int userId) { + Map<String, OverlayPaths> getOverlayPathsForLibrary(int userId) { return readUserState(userId).getSharedLibraryOverlayPaths(); } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index fb033e6594b8..a8a6bcec2313 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -40,6 +40,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; import android.content.pm.IntentFilterVerificationInfo; +import android.content.pm.overlay.OverlayPaths; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.PackageUserState; @@ -49,6 +50,7 @@ import android.content.pm.Signature; import android.content.pm.SuspendDialogInfo; import android.content.pm.UserInfo; import android.content.pm.VerifierDeviceIdentity; +import android.content.pm.overlay.OverlayPaths; import android.content.pm.parsing.PackageInfoWithoutStateUtils; import android.content.pm.parsing.component.ParsedComponent; import android.content.pm.parsing.component.ParsedIntentInfo; @@ -105,9 +107,6 @@ import com.android.permission.persistence.RuntimePermissionsState; import com.android.server.LocalServices; import com.android.server.backup.PreferredActivityBackupHelper; import com.android.server.pm.Installer.InstallerException; -import com.android.server.pm.verify.domain.DomainVerificationLegacySettings; -import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; -import com.android.server.pm.verify.domain.DomainVerificationPersistence; import com.android.server.pm.parsing.PackageInfoUtils; import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.parsing.pkg.AndroidPackageUtils; @@ -115,6 +114,9 @@ import com.android.server.pm.permission.LegacyPermissionDataProvider; import com.android.server.pm.permission.LegacyPermissionSettings; import com.android.server.pm.permission.LegacyPermissionState; import com.android.server.pm.permission.LegacyPermissionState.PermissionState; +import com.android.server.pm.verify.domain.DomainVerificationLegacySettings; +import com.android.server.pm.verify.domain.DomainVerificationManagerInternal; +import com.android.server.pm.verify.domain.DomainVerificationPersistence; import com.android.server.utils.Snappable; import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.utils.Watchable; @@ -487,7 +489,7 @@ public final class Settings implements Watchable, Snappable { // App-link priority tracking, per-user @NonNull @Watched - final WatchedSparseIntArray mNextAppLinkGeneration = new WatchedSparseIntArray(); + private final WatchedSparseIntArray mNextAppLinkGeneration = new WatchedSparseIntArray(); final StringBuilder mReadMessages = new StringBuilder(); @@ -552,6 +554,7 @@ public final class Settings implements Watchable, Snappable { mAppIds.registerObserver(mObserver); mOtherAppIds.registerObserver(mObserver); mRenamedPackages.registerObserver(mObserver); + mNextAppLinkGeneration.registerObserver(mObserver); mDefaultBrowserApp.registerObserver(mObserver); Watchable.verifyWatchedAttributes(this, mObserver); @@ -602,6 +605,7 @@ public final class Settings implements Watchable, Snappable { mAppIds.registerObserver(mObserver); mOtherAppIds.registerObserver(mObserver); mRenamedPackages.registerObserver(mObserver); + mNextAppLinkGeneration.registerObserver(mObserver); mDefaultBrowserApp.registerObserver(mObserver); Watchable.verifyWatchedAttributes(this, mObserver); @@ -649,6 +653,7 @@ public final class Settings implements Watchable, Snappable { mPastSignatures.addAll(r.mPastSignatures); mKeySetRefs.putAll(r.mKeySetRefs); mRenamedPackages.snapshot(r.mRenamedPackages); + mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration); mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp); // mReadMessages mPendingPackages.addAll(r.mPendingPackages); @@ -2707,7 +2712,6 @@ public final class Settings implements Watchable, Snappable { writeSigningKeySetLPr(serializer, pkg.keySetData); writeUpgradeKeySetsLPr(serializer, pkg.keySetData); writeKeySetAliasesLPr(serializer, pkg.keySetData); - mDomainVerificationManager.writeLegacySettings(serializer, pkg.name); writeMimeGroupLPr(serializer, pkg.mimeGroups); serializer.endTag(null, "package"); @@ -4686,26 +4690,58 @@ public final class Settings implements Watchable, Snappable { } } - String[] overlayPaths = ps.getOverlayPaths(user.id); - if (overlayPaths != null && overlayPaths.length > 0) { - pw.print(prefix); pw.println(" overlay paths:"); - for (String path : overlayPaths) { - pw.print(prefix); pw.print(" "); pw.println(path); + final OverlayPaths overlayPaths = ps.getOverlayPaths(user.id); + if (overlayPaths != null) { + if (!overlayPaths.getOverlayPaths().isEmpty()) { + pw.print(prefix); + pw.println(" overlay paths:"); + for (String path : overlayPaths.getOverlayPaths()) { + pw.print(prefix); + pw.print(" "); + pw.println(path); + } + } + if (!overlayPaths.getResourceDirs().isEmpty()) { + pw.print(prefix); + pw.println(" legacy overlay paths:"); + for (String path : overlayPaths.getResourceDirs()) { + pw.print(prefix); + pw.print(" "); + pw.println(path); + } } } - Map<String, String[]> sharedLibraryOverlayPaths = + final Map<String, OverlayPaths> sharedLibraryOverlayPaths = ps.getOverlayPathsForLibrary(user.id); if (sharedLibraryOverlayPaths != null) { - for (Map.Entry<String, String[]> libOverlayPaths : + for (Map.Entry<String, OverlayPaths> libOverlayPaths : sharedLibraryOverlayPaths.entrySet()) { - if (libOverlayPaths.getValue() == null) { + final OverlayPaths paths = libOverlayPaths.getValue(); + if (paths == null) { continue; } - pw.print(prefix); pw.print(" "); - pw.print(libOverlayPaths.getKey()); pw.println(" overlay paths:"); - for (String path : libOverlayPaths.getValue()) { - pw.print(prefix); pw.print(" "); pw.println(path); + if (!paths.getOverlayPaths().isEmpty()) { + pw.print(prefix); + pw.println(" "); + pw.print(libOverlayPaths.getKey()); + pw.println(" overlay paths:"); + for (String path : paths.getOverlayPaths()) { + pw.print(prefix); + pw.print(" "); + pw.println(path); + } + } + if (!paths.getResourceDirs().isEmpty()) { + pw.print(prefix); + pw.println(" "); + pw.print(libOverlayPaths.getKey()); + pw.println(" legacy overlay paths:"); + for (String path : paths.getResourceDirs()) { + pw.print(prefix); + pw.print(" "); + pw.println(path); + } } } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java index c521f828ade9..275dd053fdde 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java @@ -18,8 +18,10 @@ package com.android.server.pm.verify.domain; import android.Manifest; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Binder; import android.os.Process; @@ -30,10 +32,17 @@ public class DomainVerificationEnforcer { @NonNull private final Context mContext; + @NonNull + private Callback mCallback; + public DomainVerificationEnforcer(@NonNull Context context) { mContext = context; } + public void setCallback(@NonNull Callback callback) { + mCallback = callback; + } + /** * Enforced when mutating any state from shell or internally in the system process. */ @@ -67,6 +76,11 @@ public class DomainVerificationEnforcer { "Caller " + callingUid + " is not allowed to query domain verification state"); } + + mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES, + Binder.getCallingPid(), callingUid, + "Caller " + callingUid + " does not hold " + + android.Manifest.permission.QUERY_ALL_PACKAGES); break; } } @@ -84,28 +98,42 @@ public class DomainVerificationEnforcer { isAllowed = true; break; default: - // TODO(b/159952358): Remove permission check? The component package should - // have been checked when the verifier component was first scanned in PMS. - mContext.enforcePermission( - android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, - Binder.getCallingPid(), callingUid, - "Caller " + callingUid + " does not hold DOMAIN_VERIFICATION_AGENT"); + final int callingPid = Binder.getCallingPid(); + boolean isLegacyVerificationAgent = false; + if (mContext.checkPermission( + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, callingPid, + callingUid) != PackageManager.PERMISSION_GRANTED) { + isLegacyVerificationAgent = mContext.checkPermission( + android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT, + callingPid, callingUid) == PackageManager.PERMISSION_GRANTED; + if (!isLegacyVerificationAgent) { + throw new SecurityException("Caller " + callingUid + " does not hold " + + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT); + } + } + + // If the caller isn't a legacy verifier, it needs the QUERY_ALL permission + if (!isLegacyVerificationAgent) { + mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES, + callingPid, callingUid, "Caller " + callingUid + " does not hold " + + android.Manifest.permission.QUERY_ALL_PACKAGES); + } + isAllowed = proxy.isCallerVerifier(callingUid); break; } if (!isAllowed) { throw new SecurityException("Caller " + callingUid - + " is not the approved domain verification agent, isVerifier = " - + proxy.isCallerVerifier(callingUid)); + + " is not the approved domain verification agent"); } } /** * Enforced when mutating user selection state inside an exposed API method. */ - public void assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId, - @UserIdInt int targetUserId) throws SecurityException { + public boolean assertApprovedUserSelector(int callingUid, @UserIdInt int callingUserId, + @Nullable String packageName, @UserIdInt int targetUserId) throws SecurityException { if (callingUserId != targetUserId) { mContext.enforcePermission( Manifest.permission.INTERACT_ACROSS_USERS, @@ -117,12 +145,51 @@ public class DomainVerificationEnforcer { android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION, Binder.getCallingPid(), callingUid, "Caller is not allowed to edit user selections"); + + if (packageName == null) { + return true; + } + + return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); } - public void callerIsLegacyUserSelector(int callingUid) { + public boolean callerIsLegacyUserSelector(int callingUid, @UserIdInt int callingUserId, + @NonNull String packageName, @UserIdInt int targetUserId) { mContext.enforcePermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS, Binder.getCallingPid(), callingUid, "Caller is not allowed to edit user state"); + + if (callingUserId != targetUserId) { + if (mContext.checkPermission( + Manifest.permission.INTERACT_ACROSS_USERS, + Binder.getCallingPid(), callingUid) != PackageManager.PERMISSION_GRANTED) { + // Legacy API did not enforce this, so for backwards compatibility, fail silently + return false; + } + } + + return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); + } + + public boolean callerIsLegacyUserQuerent(int callingUid, @UserIdInt int callingUserId, + @NonNull String packageName, @UserIdInt int targetUserId) { + if (callingUserId != targetUserId) { + // The legacy API enforces the _FULL variant, so maintain that here + mContext.enforcePermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, + Binder.getCallingPid(), callingUid, + "Caller is not allowed to edit other users"); + } + + return !mCallback.filterAppAccess(packageName, callingUid, targetUserId); + } + + public interface Callback { + /** + * @return true if access to the given package should be filtered and the method failed as + * if the package was not installed + */ + boolean filterAppAccess(@NonNull String packageName, int callingUid, @UserIdInt int userId); } } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java index 0474d78a3e53..50fd6e3ddea1 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java @@ -174,8 +174,10 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan * Set aside a legacy user selection that will be restored to a pending * {@link DomainVerificationPkgState} once it's added through * {@link #addPackage(PackageSetting)}. + * + * @return true if state changed successfully */ - void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state); + boolean setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state); /** * Until the legacy APIs are entirely removed, returns the legacy state from the previously @@ -184,12 +186,6 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan int getLegacyState(@NonNull String packageName, @UserIdInt int userId); /** - * Serialize a legacy setting that wasn't attached yet. - * TODO: Does this even matter? Should consider for removal. - */ - void writeLegacySettings(TypedXmlSerializer serializer, String name); - - /** * Print the verification state and user selection state of a package. * * @param packageName the package whose state to change, or all packages if none is @@ -235,7 +231,8 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan throws IllegalArgumentException, NameNotFoundException; - interface Connection extends Function<String, PackageSetting> { + interface Connection extends DomainVerificationEnforcer.Callback, + Function<String, PackageSetting> { /** * Notify that a settings change has been made and that eventually diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index e24e5bbfa4f6..fa0327414914 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -47,12 +47,12 @@ import com.android.server.SystemConfig; import com.android.server.SystemService; import com.android.server.compat.PlatformCompat; import com.android.server.pm.PackageSetting; +import com.android.server.pm.parsing.pkg.AndroidPackage; import com.android.server.pm.verify.domain.models.DomainVerificationPkgState; import com.android.server.pm.verify.domain.models.DomainVerificationStateMap; import com.android.server.pm.verify.domain.models.DomainVerificationUserState; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy; import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyUnavailable; -import com.android.server.pm.parsing.pkg.AndroidPackage; import org.xmlpull.v1.XmlPullParserException; @@ -92,9 +92,9 @@ public class DomainVerificationService extends SystemService * immediately attached once its available. * <p> * Generally this should be not accessed directly. Prefer calling {@link - * #getAndValidateAttachedLocked(UUID, Set, boolean)}. + * #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer)}. * - * @see #getAndValidateAttachedLocked(UUID, Set, boolean) + * @see #getAndValidateAttachedLocked(UUID, Set, boolean, int, Integer) **/ @GuardedBy("mLock") @NonNull @@ -160,6 +160,7 @@ public class DomainVerificationService extends SystemService @Override public void setConnection(@NonNull Connection connection) { mConnection = connection; + mEnforcer.setCallback(mConnection); } @NonNull @@ -285,7 +286,7 @@ public class DomainVerificationService extends SystemService mEnforcer.assertApprovedVerifier(callingUid, mProxy); synchronized (mLock) { DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, - true /* forAutoVerify */); + true /* forAutoVerify */, callingUid, null /* userId */); ArrayMap<String, Integer> stateMap = pkgState.getStateMap(); for (String domain : domains) { Integer previousState = stateMap.get(domain); @@ -389,8 +390,10 @@ public class DomainVerificationService extends SystemService public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName, boolean allowed, @UserIdInt int userId) throws NameNotFoundException { - mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(), - mConnection.getCallingUserId(), userId); + if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(), + mConnection.getCallingUserId(), packageName, userId)) { + throw DomainVerificationUtils.throwPackageUnavailable(packageName); + } synchronized (mLock) { DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); if (pkgState == null) { @@ -455,11 +458,18 @@ public class DomainVerificationService extends SystemService public void setDomainVerificationUserSelection(@NonNull UUID domainSetId, @NonNull Set<String> domains, boolean enabled, @UserIdInt int userId) throws InvalidDomainSetException, NameNotFoundException { - mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(), - mConnection.getCallingUserId(), userId); synchronized (mLock) { + final int callingUid = mConnection.getCallingUid(); + // Pass null for package name here and do the app visibility enforcement inside + // getAndValidateAttachedLocked instead, since this has to fail with the same invalid + // ID reason if the target app is invisible + if (!mEnforcer.assertApprovedUserSelector(callingUid, mConnection.getCallingUserId(), + null /* packageName */, userId)) { + throw new InvalidDomainSetException(domainSetId, null, + InvalidDomainSetException.REASON_ID_INVALID); + } DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, - false /* forAutoVerify */); + false /* forAutoVerify */, callingUid, userId); DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId); if (enabled) { userState.addHosts(domains); @@ -556,8 +566,10 @@ public class DomainVerificationService extends SystemService @Override public DomainVerificationUserSelection getDomainVerificationUserSelection( @NonNull String packageName, @UserIdInt int userId) throws NameNotFoundException { - mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(), - mConnection.getCallingUserId(), userId); + if (!mEnforcer.assertApprovedUserSelector(mConnection.getCallingUid(), + mConnection.getCallingUserId(), packageName, userId)) { + throw DomainVerificationUtils.throwPackageUnavailable(packageName); + } synchronized (mLock) { DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName); if (pkgState == null) { @@ -844,23 +856,27 @@ public class DomainVerificationService extends SystemService } @Override - public void setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, int state) { - mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid()); + public boolean setLegacyUserState(@NonNull String packageName, @UserIdInt int userId, + int state) { + if (!mEnforcer.callerIsLegacyUserSelector(mConnection.getCallingUid(), + mConnection.getCallingUserId(), packageName, userId)) { + return false; + } mLegacySettings.add(packageName, userId, state); mConnection.scheduleWriteSettings(); + return true; } @Override public int getLegacyState(@NonNull String packageName, @UserIdInt int userId) { + if (!mEnforcer.callerIsLegacyUserQuerent(mConnection.getCallingUid(), + mConnection.getCallingUserId(), packageName, userId)) { + return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED; + } return mLegacySettings.getUserState(packageName, userId); } @Override - public void writeLegacySettings(TypedXmlSerializer serializer, String name) { - - } - - @Override public void clearPackage(@NonNull String packageName) { synchronized (mLock) { mAttachedPkgStates.remove(packageName); @@ -935,10 +951,14 @@ public class DomainVerificationService extends SystemService * Validates parameters provided by an external caller. Checks that an ID is still live and that * any provided domains are valid. Should be called at the beginning of each API that takes in a * {@link UUID} domain set ID. + * + * @param userIdForFilter which user to filter app access to, or null if the caller has already + * validated package visibility */ @GuardedBy("mLock") private DomainVerificationPkgState getAndValidateAttachedLocked(@NonNull UUID domainSetId, - @NonNull Set<String> domains, boolean forAutoVerify) + @NonNull Set<String> domains, boolean forAutoVerify, int callingUid, + @Nullable Integer userIdForFilter) throws InvalidDomainSetException, NameNotFoundException { if (domainSetId == null) { throw new InvalidDomainSetException(null, null, @@ -952,6 +972,13 @@ public class DomainVerificationService extends SystemService } String pkgName = pkgState.getPackageName(); + + if (userIdForFilter != null + && mConnection.filterAppAccess(pkgName, callingUid, userIdForFilter)) { + throw new InvalidDomainSetException(domainSetId, null, + InvalidDomainSetException.REASON_ID_INVALID); + } + PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName); if (pkgSetting == null || pkgSetting.getPkg() == null) { throw DomainVerificationUtils.throwPackageUnavailable(pkgName); diff --git a/services/core/java/com/android/server/utils/Watchable.java b/services/core/java/com/android/server/utils/Watchable.java index f936693bd621..44a74593cda2 100644 --- a/services/core/java/com/android/server/utils/Watchable.java +++ b/services/core/java/com/android/server/utils/Watchable.java @@ -19,8 +19,8 @@ package com.android.server.utils; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Build; +import android.util.Log; -import java.lang.annotation.Annotation; import java.lang.reflect.Field; /** @@ -61,40 +61,54 @@ public interface Watchable { public void dispatchChange(@Nullable Watchable what); /** - * Return true if the field is tagged with @Watched - */ - private static boolean isWatched(Field f) { - for (Annotation a : f.getDeclaredAnnotations()) { - if (a.annotationType().equals(Watched.class)) { - return true; - } - } - return false; - } - - /** * Verify that all @Watched {@link Watchable} attributes are being watched by this * class. This requires reflection and only runs in engineering or user debug * builds. + * @param base The object that contains watched attributes. + * @param observer The {@link Watcher} that should be watching these attributes. + * @param logOnly If true then log errors; if false then throw an RuntimeExecption on error. */ - static void verifyWatchedAttributes(Object base, Watcher observer) { - if (Build.IS_ENG || Build.IS_USERDEBUG) { - for (Field f : base.getClass().getDeclaredFields()) { + static void verifyWatchedAttributes(Object base, Watcher observer, boolean logOnly) { + if (!(Build.IS_ENG || Build.IS_USERDEBUG)) { + return; + } + for (Field f : base.getClass().getDeclaredFields()) { + if (f.getAnnotation(Watched.class) != null) { + final String fn = base.getClass().getName() + "." + f.getName(); try { - final boolean flagged = isWatched(f); + f.setAccessible(true); final Object o = f.get(base); - final boolean watchable = o instanceof Watchable; - if (flagged && watchable) { - Watchable attr = (Watchable) f.get(base); + if (o instanceof Watchable) { + Watchable attr = (Watchable) (o); if (attr != null && !attr.isRegisteredObserver(observer)) { - throw new RuntimeException(f.getName() + " missing an observer"); + if (logOnly) { + Log.e("Watchable", fn + " missing an observer"); + } else { + throw new RuntimeException("Watchable " + fn + + " missing an observer"); + } } } } catch (IllegalAccessException e) { // The field is protected; ignore it. Other exceptions that may be thrown by // Field.get() are allowed to roll up. + if (logOnly) { + Log.e("Watchable", fn + " not visible"); + } else { + throw new RuntimeException("Watchable " + fn + " not visible"); + } } } } } + + /** + * Verify that all @Watched {@link Watchable} attributes are being watched by this + * class. This calls verifyWatchedAttributes() with logOnly set to false. + * @param base The object that contains watched attributes. + * @param observer The {@link Watcher} that should be watching these attributes. + */ + static void verifyWatchedAttributes(Object base, Watcher observer) { + verifyWatchedAttributes(base, observer, false); + } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 61fe023ae1b9..5460e36a5f1b 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2086,6 +2086,7 @@ public class DisplayPolicy { pi.getResDir(), null /* splitResDirs */, pi.getOverlayDirs(), + pi.getOverlayPaths(), pi.getApplicationInfo().sharedLibraryFiles, mDisplayContent.getDisplayId(), null /* overrideConfig */, diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index ceebe9550846..bd93e045cdc7 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -847,6 +847,8 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mWmService.openSurfaceTransaction(); try { applySurfaceChangesTransaction(); + // Send any pending task-info changes that were queued-up during a layout deferment + mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); mWmService.mSyncEngine.onSurfacePlacement(); } catch (RuntimeException e) { Slog.wtf(TAG, "Unhandled exception in Window Manager", e); @@ -859,8 +861,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> } } - // Send any pending task-info changes that were queued-up during a layout deferment - mWmService.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); mWmService.mAnimator.executeAfterPrepareSurfacesRunnables(); checkAppTransitionReady(surfacePlacer); diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 1f8daf6f361f..8b186796db8d 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -104,9 +104,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { final boolean mCanAddInternalSystemWindow; private final boolean mCanStartTasksFromRecents; - // If non-system overlays from this process can be hidden by the user or app using - // HIDE_NON_SYSTEM_OVERLAY_WINDOWS. - final boolean mOverlaysCanBeHidden; final boolean mCanCreateSystemApplicationOverlay; final boolean mCanHideNonSystemOverlayWindows; final boolean mCanAcquireSleepToken; @@ -136,8 +133,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { == PERMISSION_GRANTED; mCanStartTasksFromRecents = service.mContext.checkCallingOrSelfPermission( START_TASKS_FROM_RECENTS) == PERMISSION_GRANTED; - mOverlaysCanBeHidden = !mCanAddInternalSystemWindow - && !mService.mAtmInternal.isCallerRecents(mUid); mCanAcquireSleepToken = service.mContext.checkCallingOrSelfPermission(DEVICE_POWER) == PERMISSION_GRANTED; mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications; @@ -253,11 +248,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } @Override - public void setTransparentRegion(IWindow window, Region region) { - mService.setTransparentRegionWindow(this, window, region); - } - - @Override public void setInsets(IWindow window, int touchableInsets, Rect contentInsets, Rect visibleInsets, Region touchableArea) { mService.setInsetsWindow(this, window, touchableInsets, contentInsets, @@ -682,7 +672,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { boolean changed; - if (mOverlaysCanBeHidden && !mCanCreateSystemApplicationOverlay) { + if (!mCanAddInternalSystemWindow && !mCanCreateSystemApplicationOverlay) { // We want to track non-system apps adding alert windows so we can post an // on-going notification for the user to control their visibility. if (visible) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 3c7bab3da279..23487252c91c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2150,23 +2150,6 @@ public class WindowManagerService extends IWindowManager.Stub Slog.i(tag, s, e); } - void setTransparentRegionWindow(Session session, IWindow client, Region region) { - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (mGlobalLock) { - WindowState w = windowForClientLocked(session, client, false); - ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE transparentRegionHint=%s: %s", - region, w); - - if ((w != null) && w.mHasSurface) { - w.mWinAnimator.setTransparentRegionHintLocked(region); - } - } - } finally { - Binder.restoreCallingIdentity(origId); - } - } - void setInsetsWindow(Session session, IWindow client, int touchableInsets, Rect contentInsets, Rect visibleInsets, Region touchableRegion) { int uid = Binder.getCallingUid(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index fd3d9ba499b7..a94b0aa9b72f 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3100,7 +3100,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } void setForceHideNonSystemOverlayWindowIfNeeded(boolean forceHide) { - if (!mSession.mOverlaysCanBeHidden + if (mSession.mCanAddInternalSystemWindow || (!isSystemAlertWindowType(mAttrs.type) && mAttrs.type != TYPE_TOAST)) { return; } diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index fe70dc12d3a2..ece256e8c591 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -678,14 +678,6 @@ class WindowStateAnimator { } } - void setTransparentRegionHintLocked(final Region region) { - if (mSurfaceController == null) { - Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true"); - return; - } - mSurfaceController.setTransparentRegionHint(region); - } - boolean setWallpaperOffset(int dx, int dy, float scale) { if (mXOffset == dx && mYOffset == dy && Float.compare(mWallpaperScale, scale) == 0) { return false; diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java index 82ba3c188b76..636f0bb6086f 100644 --- a/services/core/java/com/android/server/wm/WindowSurfaceController.java +++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java @@ -194,22 +194,6 @@ class WindowSurfaceController { return true; } - void setTransparentRegionHint(final Region region) { - if (mSurfaceControl == null) { - Slog.w(TAG, "setTransparentRegionHint: null mSurface after mHasSurface true"); - return; - } - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setTransparentRegion"); - mService.openSurfaceTransaction(); - try { - getGlobalTransaction().setTransparentRegionHint(mSurfaceControl, region); - } finally { - mService.closeSurfaceTransaction("setTransparentRegion"); - if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, - "<<< CLOSE TRANSACTION setTransparentRegion"); - } - } - void setOpaque(boolean isOpaque) { ProtoLog.i(WM_SHOW_TRANSACTIONS, "SURFACE isOpaque=%b: %s", isOpaque, title); diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index d863194d6889..c2e0b776fc60 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -18,6 +18,7 @@ package com.android.server.pm.test.verify.domain import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.content.pm.PackageUserState import android.content.pm.verify.domain.DomainVerificationManager import android.content.pm.parsing.component.ParsedActivity @@ -25,7 +26,6 @@ import android.content.pm.parsing.component.ParsedIntentInfo import android.os.Build import android.os.Process import android.util.ArraySet -import android.util.Singleton import android.util.SparseArray import androidx.test.platform.app.InstrumentationRegistry import com.android.server.pm.PackageSetting @@ -46,14 +46,11 @@ import org.mockito.Mockito.anyLong import org.mockito.Mockito.anyString import org.mockito.Mockito.eq import org.mockito.Mockito.verifyNoMoreInteractions -import org.testng.Assert.assertThrows import java.io.File import java.util.UUID import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger -private typealias Enforcer = DomainVerificationEnforcer - @RunWith(Parameterized::class) class DomainVerificationEnforcerTest { @@ -64,64 +61,27 @@ class DomainVerificationEnforcerTest { private const val VERIFIER_UID = Process.FIRST_APPLICATION_UID + 1 private const val NON_VERIFIER_UID = Process.FIRST_APPLICATION_UID + 2 - private const val TEST_PKG = "com.test" + private const val VISIBLE_PKG = "com.test.visible" + private val VISIBLE_UUID = UUID.fromString("8db01272-270d-4606-a3db-bb35228ff9a2") + private const val INVISIBLE_PKG = "com.test.invisible" + private val INVISIBLE_UUID = UUID.fromString("16dcb029-d96c-4a19-833a-4c9d72e2ebc3") @JvmStatic @Parameterized.Parameters(name = "{0}") fun parameters(): Array<Any> { - val makeEnforcer: (Context) -> DomainVerificationEnforcer = { - DomainVerificationEnforcer(it) - } + val visiblePkg = mockPkg(VISIBLE_PKG) + val visiblePkgSetting = mockPkgSetting(VISIBLE_PKG, VISIBLE_UUID) + val invisiblePkg = mockPkg(INVISIBLE_PKG) + val invisiblePkgSetting = mockPkgSetting(INVISIBLE_PKG, INVISIBLE_UUID) - val mockPkg = mockThrowOnUnmocked<AndroidPackage> { - whenever(packageName) { TEST_PKG } - whenever(targetSdkVersion) { Build.VERSION_CODES.S } - whenever(activities) { - listOf( - ParsedActivity().apply { - addIntent( - ParsedIntentInfo().apply { - autoVerify = true - addAction(Intent.ACTION_VIEW) - addCategory(Intent.CATEGORY_BROWSABLE) - addCategory(Intent.CATEGORY_DEFAULT) - addDataScheme("https") - addDataAuthority("example.com", null) - } - ) + val makeEnforcer: (Context) -> DomainVerificationEnforcer = { + DomainVerificationEnforcer(it).apply { + setCallback(mockThrowOnUnmocked { + whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false } + whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) { + true } - ) - } - } - - val uuid = UUID.randomUUID() - - // TODO: PackageSetting field encapsulation to move to whenever(name) - val mockPkgSetting = spyThrowOnUnmocked( - PackageSetting( - TEST_PKG, - TEST_PKG, - File("/test"), - null, - null, - null, - null, - 1, - 0, - 0, - 0, - null, - null, - null, - uuid - ) - ) { - whenever(getPkg()) { mockPkg } - whenever(domainSetId) { uuid } - whenever(userState) { - SparseArray<PackageUserState>().apply { - this[0] = PackageUserState() - } + }) } } @@ -129,142 +89,160 @@ class DomainVerificationEnforcerTest { { val callingUidInt = AtomicInteger(-1) val callingUserIdInt = AtomicInteger(-1) - Triple( - callingUidInt, callingUserIdInt, DomainVerificationService( - it, - mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } }, - mockThrowOnUnmocked { - whenever( - isChangeEnabled( - anyLong(), - any() - ) - ) { true } - }).apply { - setConnection(mockThrowOnUnmocked { - whenever(callingUid) { callingUidInt.get() } - whenever(callingUserId) { callingUserIdInt.get() } - whenever(getPackageSettingLocked(TEST_PKG)) { mockPkgSetting } - whenever(getPackageLocked(TEST_PKG)) { mockPkg } - whenever(schedule(anyInt(), any())) - whenever(scheduleWriteSettings()) - }) + + val connection: DomainVerificationManagerInternal.Connection = + mockThrowOnUnmocked { + whenever(callingUid) { callingUidInt.get() } + whenever(callingUserId) { callingUserIdInt.get() } + whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting } + whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg } + whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting } + whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg } + whenever(schedule(anyInt(), any())) + whenever(scheduleWriteSettings()) + whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false } + whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) { + true + } } - ) + val service = DomainVerificationService( + it, + mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } }, + mockThrowOnUnmocked { + whenever( + isChangeEnabled( + anyLong(), + any() + ) + ) { true } + }).apply { + setConnection(connection) + } + + Triple(callingUidInt, callingUserIdInt, service) } fun enforcer( type: Type, name: String, - block: DomainVerificationEnforcer.( - callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy - ) -> Unit - ) = Params( - type, - makeEnforcer, - name - ) { enforcer, callingUid, callingUserId, userId, proxy -> - enforcer.block(callingUid, callingUserId, userId, proxy) + block: DomainVerificationEnforcer.(Params.Input<DomainVerificationEnforcer>) -> Any? + ) = Params(type, makeEnforcer, name) { + it.target.block(it) } fun service( type: Type, name: String, - block: DomainVerificationService.( - callingUid: Int, callingUserId: Int, userId: Int - ) -> Unit - ) = Params( - type, - makeService, - name - ) { uidAndUserIdAndService, callingUid, callingUserId, userId, proxy -> - val (callingUidInt, callingUserIdInt, service) = uidAndUserIdAndService - callingUidInt.set(callingUid) - callingUserIdInt.set(callingUserId) - service.setProxy(proxy) - service.addPackage(mockPkgSetting) - service.block(callingUid, callingUserId, userId) + block: DomainVerificationService.(Params.Input<Triple<AtomicInteger, AtomicInteger, DomainVerificationService>>) -> Any? + ) = Params(type, makeService, name) { + val (callingUidInt, callingUserIdInt, service) = it.target + callingUidInt.set(it.callingUid) + callingUserIdInt.set(it.callingUserId) + service.proxy = it.proxy + service.addPackage(visiblePkgSetting) + service.addPackage(invisiblePkgSetting) + service.block(it) } return arrayOf( - enforcer(Type.INTERNAL, "internal") { callingUid, _, _, _ -> - assertInternal(callingUid) + enforcer(Type.INTERNAL, "internal") { + assertInternal(it.callingUid) }, - enforcer(Type.QUERENT, "approvedQuerent") { callingUid, _, _, proxy -> - assertApprovedQuerent(callingUid, proxy) + enforcer(Type.QUERENT, "approvedQuerent") { + assertApprovedQuerent(it.callingUid, it.proxy) }, - enforcer(Type.VERIFIER, "approvedVerifier") { callingUid, _, _, proxy -> - assertApprovedVerifier(callingUid, proxy) + enforcer(Type.VERIFIER, "approvedVerifier") { + assertApprovedVerifier(it.callingUid, it.proxy) }, enforcer( Type.SELECTOR, "approvedUserSelector" - ) { callingUid, callingUserId, userId, _ -> - assertApprovedUserSelector(callingUid, callingUserId, userId) + ) { + assertApprovedUserSelector( + it.callingUid, it.callingUserId, + it.targetPackageName, it.userId + ) }, - - service(Type.INTERNAL, "setStatusInternalPackageName") { _, _, _ -> + service(Type.INTERNAL, "setStatusInternalPackageName") { setDomainVerificationStatusInternal( - TEST_PKG, + it.targetPackageName, DomainVerificationManager.STATE_SUCCESS, ArraySet(setOf("example.com")) ) }, - service(Type.INTERNAL, "setUserSelectionInternal") { _, _, userId -> + service(Type.INTERNAL, "setUserSelectionInternal") { setDomainVerificationUserSelectionInternal( - userId, - TEST_PKG, + it.userId, + it.targetPackageName, false, ArraySet(setOf("example.com")) ) }, - service(Type.INTERNAL, "verifyPackages") { _, _, _ -> - verifyPackages(listOf(TEST_PKG), true) + service(Type.INTERNAL, "verifyPackages") { + verifyPackages(listOf(it.targetPackageName), true) }, - service(Type.INTERNAL, "clearState") { _, _, _ -> - clearDomainVerificationState(listOf(TEST_PKG)) + service(Type.INTERNAL, "clearState") { + clearDomainVerificationState(listOf(it.targetPackageName)) }, - service(Type.INTERNAL, "clearUserSelections") { _, _, userId -> - clearUserSelections(listOf(TEST_PKG), userId) + service(Type.INTERNAL, "clearUserSelections") { + clearUserSelections(listOf(it.targetPackageName), it.userId) }, - service(Type.VERIFIER, "getPackageNames") { _, _, _ -> + service(Type.VERIFIER, "getPackageNames") { validVerificationPackageNames }, - service(Type.QUERENT, "getInfo") { _, _, _ -> - getDomainVerificationInfo(TEST_PKG) + service(Type.QUERENT, "getInfo") { + getDomainVerificationInfo(it.targetPackageName) }, - service(Type.VERIFIER, "setStatus") { _, _, _ -> + service(Type.VERIFIER, "setStatus") { setDomainVerificationStatus( - uuid, + it.targetDomainSetId, setOf("example.com"), DomainVerificationManager.STATE_SUCCESS ) }, - service(Type.VERIFIER, "setStatusInternalUid") { callingUid, _, _ -> + service(Type.VERIFIER, "setStatusInternalUid") { setDomainVerificationStatusInternal( - callingUid, - uuid, + it.callingUid, + it.targetDomainSetId, setOf("example.com"), DomainVerificationManager.STATE_SUCCESS ) }, - service(Type.SELECTOR, "setLinkHandlingAllowed") { _, _, _ -> - setDomainVerificationLinkHandlingAllowed(TEST_PKG, true) + service(Type.SELECTOR, "setLinkHandlingAllowed") { + setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true) }, - service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { _, _, userId -> - setDomainVerificationLinkHandlingAllowed(TEST_PKG, true, userId) + service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") { + setDomainVerificationLinkHandlingAllowed(it.targetPackageName, true, it.userId) }, - service(Type.SELECTOR, "getUserSelection") { _, _, _ -> - getDomainVerificationUserSelection(TEST_PKG) + service(Type.SELECTOR, "getUserSelection") { + getDomainVerificationUserSelection(it.targetPackageName) }, - service(Type.SELECTOR_USER, "getUserSelectionUserId") { _, _, userId -> - getDomainVerificationUserSelection(TEST_PKG, userId) + service(Type.SELECTOR_USER, "getUserSelectionUserId") { + getDomainVerificationUserSelection(it.targetPackageName, it.userId) }, - service(Type.SELECTOR, "setUserSelection") { _, _, _ -> - setDomainVerificationUserSelection(uuid, setOf("example.com"), true) + service(Type.SELECTOR, "setUserSelection") { + setDomainVerificationUserSelection( + it.targetDomainSetId, + setOf("example.com"), + true + ) + }, + service(Type.SELECTOR_USER, "setUserSelectionUserId") { + setDomainVerificationUserSelection( + it.targetDomainSetId, + setOf("example.com"), + true, + it.userId + ) }, - service(Type.SELECTOR_USER, "setUserSelectionUserId") { _, _, userId -> - setDomainVerificationUserSelection(uuid, setOf("example.com"), true, userId) + service(Type.LEGACY_SELECTOR, "setLegacyUserState") { + setLegacyUserState( + it.targetPackageName, it.userId, + PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER + ) + }, + service(Type.LEGACY_QUERENT, "getLegacyUserState") { + getLegacyState(it.targetPackageName, it.userId) }, ) } @@ -273,9 +251,7 @@ class DomainVerificationEnforcerTest { val type: Type, val construct: (context: Context) -> T, val name: String, - private val method: ( - T, callingUid: Int, callingUserId: Int, userId: Int, proxy: DomainVerificationProxy - ) -> Unit + private val method: (Input<T>) -> Any? ) { override fun toString() = "${type}_$name" @@ -284,10 +260,79 @@ class DomainVerificationEnforcerTest { callingUid: Int, callingUserId: Int, userId: Int, + targetPackageName: String, + targetDomainSetId: UUID, proxy: DomainVerificationProxy - ) { - @Suppress("UNCHECKED_CAST") - method(target as T, callingUid, callingUserId, userId, proxy) + ): Any? = method( + Input( + @Suppress("UNCHECKED_CAST") + target as T, + callingUid, + callingUserId, + userId, + targetPackageName, + targetDomainSetId, + proxy + ) + ) + + data class Input<T>( + val target: T, + val callingUid: Int, + val callingUserId: Int, + val userId: Int, + val targetPackageName: String, + val targetDomainSetId: UUID, + val proxy: DomainVerificationProxy + ) + } + + fun mockPkg(packageName: String) = mockThrowOnUnmocked<AndroidPackage> { + whenever(this.packageName) { packageName } + whenever(targetSdkVersion) { Build.VERSION_CODES.S } + whenever(activities) { + listOf( + ParsedActivity().apply { + addIntent( + ParsedIntentInfo().apply { + autoVerify = true + addAction(Intent.ACTION_VIEW) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("https") + addDataAuthority("example.com", null) + } + ) + } + ) + } + } + + fun mockPkgSetting(packageName: String, domainSetId: UUID) = spyThrowOnUnmocked( + PackageSetting( + packageName, + packageName, + File("/test"), + null, + null, + null, + null, + 1, + 0, + 0, + 0, + null, + null, + null, + domainSetId + ) + ) { + whenever(getPkg()) { mockPkg(packageName) } + whenever(this.domainSetId) { domainSetId } + whenever(userState) { + SparseArray<PackageUserState>().apply { + this[0] = PackageUserState() + } } } } @@ -309,6 +354,8 @@ class DomainVerificationEnforcerTest { Type.VERIFIER -> approvedVerifier() Type.SELECTOR -> approvedUserSelector(verifyCrossUser = false) Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true) + Type.LEGACY_QUERENT -> legacyQuerent() + Type.LEGACY_SELECTOR -> legacyUserSelector() }.run { /*exhaust*/ } } @@ -316,24 +363,30 @@ class DomainVerificationEnforcerTest { val context: Context = mockThrowOnUnmocked() val target = params.construct(context) - INTERNAL_UIDS.forEach { runMethod(target, it) } - assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) } - assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) } + // Internal doesn't care about visibility + listOf(true, false).forEach { visible -> + INTERNAL_UIDS.forEach { runMethod(target, it, visible) } + assertFails { runMethod(target, VERIFIER_UID, visible) } + assertFails { + runMethod(target, NON_VERIFIER_UID, visible) + } + } } fun approvedQuerent() { val allowUserSelection = AtomicBoolean(false) + val allowPreferredApps = AtomicBoolean(false) + val allowQueryAll = AtomicBoolean(false) val context: Context = mockThrowOnUnmocked { - whenever( - enforcePermission( - eq(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION), - anyInt(), anyInt(), anyString() - ) - ) { - if (!allowUserSelection.get()) { - throw SecurityException() - } - } + initPermission( + allowUserSelection, + android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION + ) + initPermission( + allowPreferredApps, + android.Manifest.permission.SET_PREFERRED_APPLICATIONS + ) + initPermission(allowQueryAll, android.Manifest.permission.QUERY_ALL_PACKAGES) } val target = params.construct(context) @@ -341,27 +394,41 @@ class DomainVerificationEnforcerTest { verifyNoMoreInteractions(context) + assertFails { runMethod(target, VERIFIER_UID) } + assertFails { runMethod(target, NON_VERIFIER_UID) } + + // Check that the verifier only needs QUERY_ALL to pass + allowQueryAll.set(true) runMethod(target, VERIFIER_UID) - assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) } + allowQueryAll.set(false) + + allowPreferredApps.set(true) + + assertFails { runMethod(target, NON_VERIFIER_UID) } allowUserSelection.set(true) + assertFails { runMethod(target, NON_VERIFIER_UID) } + + allowQueryAll.set(true) + runMethod(target, NON_VERIFIER_UID) } fun approvedVerifier() { - val shouldThrow = AtomicBoolean(false) + val allowDomainVerificationAgent = AtomicBoolean(false) + val allowIntentVerificationAgent = AtomicBoolean(false) + val allowQueryAll = AtomicBoolean(false) val context: Context = mockThrowOnUnmocked { - whenever( - enforcePermission( - eq(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT), - anyInt(), anyInt(), anyString() - ) - ) { - if (shouldThrow.get()) { - throw SecurityException() - } - } + initPermission( + allowDomainVerificationAgent, + android.Manifest.permission.DOMAIN_VERIFICATION_AGENT + ) + initPermission( + allowIntentVerificationAgent, + android.Manifest.permission.INTENT_FILTER_VERIFICATION_AGENT + ) + initPermission(allowQueryAll, android.Manifest.permission.QUERY_ALL_PACKAGES) } val target = params.construct(context) @@ -369,94 +436,241 @@ class DomainVerificationEnforcerTest { verifyNoMoreInteractions(context) + assertFails { runMethod(target, VERIFIER_UID) } + assertFails { runMethod(target, NON_VERIFIER_UID) } + + allowDomainVerificationAgent.set(true) + + assertFails { runMethod(target, VERIFIER_UID) } + assertFails { runMethod(target, NON_VERIFIER_UID) } + + allowQueryAll.set(true) + runMethod(target, VERIFIER_UID) - assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) } + assertFails { runMethod(target, NON_VERIFIER_UID) } - shouldThrow.set(true) + // Check that v1 verifiers are also allowed through + allowDomainVerificationAgent.set(false) + allowIntentVerificationAgent.set(true) - assertThrows(SecurityException::class.java) { runMethod(target, VERIFIER_UID) } - assertThrows(SecurityException::class.java) { runMethod(target, NON_VERIFIER_UID) } + runMethod(target, VERIFIER_UID) + assertFails { runMethod(target, NON_VERIFIER_UID) } } fun approvedUserSelector(verifyCrossUser: Boolean) { - val allowUserSelection = AtomicBoolean(true) - val allowInteractAcrossUsers = AtomicBoolean(true) + val allowUserSelection = AtomicBoolean(false) + val allowInteractAcrossUsers = AtomicBoolean(false) val context: Context = mockThrowOnUnmocked { - whenever( - enforcePermission( - eq(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION), - anyInt(), anyInt(), anyString() - ) - ) { - if (!allowUserSelection.get()) { - throw SecurityException() + initPermission( + allowUserSelection, + android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION + ) + initPermission( + allowInteractAcrossUsers, + android.Manifest.permission.INTERACT_ACROSS_USERS + ) + } + val target = params.construct(context) + + fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { + // User selector makes no distinction by UID + val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID + if (throws) { + allUids.forEach { + assertFails { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } } - } - whenever( - enforcePermission( - eq(android.Manifest.permission.INTERACT_ACROSS_USERS), - anyInt(), anyInt(), anyString() - ) - ) { - if (!allowInteractAcrossUsers.get()) { - throw SecurityException() + } else { + allUids.forEach { + runMethod(target, it, visible = true, callingUserId, targetUserId) } } - } - val target = params.construct(context) - fun runEachTestCaseWrapped( - callingUserId: Int, - targetUserId: Int, - block: (testCase: () -> Unit) -> Unit = { it.invoke() } - ) { - block { runMethod(target, VERIFIER_UID, callingUserId, targetUserId) } - block { runMethod(target, NON_VERIFIER_UID, callingUserId, targetUserId) } + // User selector doesn't use QUERY_ALL, so the invisible package should always fail + allUids.forEach { + assertFails { + runMethod(target, it, visible = false, callingUserId, targetUserId) + } + } } val callingUserId = 0 val notCallingUserId = 1 - runEachTestCaseWrapped(callingUserId, callingUserId) + runTestCases(callingUserId, callingUserId, throws = true) if (verifyCrossUser) { - runEachTestCaseWrapped(callingUserId, notCallingUserId) + runTestCases(callingUserId, notCallingUserId, throws = true) } - allowInteractAcrossUsers.set(false) + allowUserSelection.set(true) + + runTestCases(callingUserId, callingUserId, throws = false) + if (verifyCrossUser) { + runTestCases(callingUserId, notCallingUserId, throws = true) + } - runEachTestCaseWrapped(callingUserId, callingUserId) + allowInteractAcrossUsers.set(true) + runTestCases(callingUserId, callingUserId, throws = false) if (verifyCrossUser) { - runEachTestCaseWrapped(callingUserId, notCallingUserId) { - assertThrows(SecurityException::class.java, it) + runTestCases(callingUserId, notCallingUserId, throws = false) + } + } + + private fun legacyUserSelector() { + val allowInteractAcrossUsers = AtomicBoolean(false) + val allowPreferredApps = AtomicBoolean(false) + val context: Context = mockThrowOnUnmocked { + initPermission( + allowInteractAcrossUsers, + android.Manifest.permission.INTERACT_ACROSS_USERS + ) + initPermission( + allowPreferredApps, + android.Manifest.permission.SET_PREFERRED_APPLICATIONS + ) + } + val target = params.construct(context) + + fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { + // Legacy makes no distinction by UID + val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID + if (throws) { + allUids.forEach { + assertFails { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + } else { + allUids.forEach { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + + // Legacy doesn't use QUERY_ALL, so the invisible package should always fail + allUids.forEach { + assertFails { + runMethod(target, it, visible = false, callingUserId, targetUserId) + } } } - allowUserSelection.set(false) + val callingUserId = 0 + val notCallingUserId = 1 + + runTestCases(callingUserId, callingUserId, throws = true) + runTestCases(callingUserId, notCallingUserId, throws = true) + + allowPreferredApps.set(true) - runEachTestCaseWrapped(callingUserId, callingUserId) { - assertThrows(SecurityException::class.java, it) + runTestCases(callingUserId, callingUserId, throws = false) + runTestCases(callingUserId, notCallingUserId, throws = true) + + allowInteractAcrossUsers.set(true) + + runTestCases(callingUserId, callingUserId, throws = false) + runTestCases(callingUserId, notCallingUserId, throws = false) + } + + private fun legacyQuerent() { + val allowInteractAcrossUsers = AtomicBoolean(false) + val allowInteractAcrossUsersFull = AtomicBoolean(false) + val context: Context = mockThrowOnUnmocked { + initPermission( + allowInteractAcrossUsers, + android.Manifest.permission.INTERACT_ACROSS_USERS + ) + initPermission( + allowInteractAcrossUsersFull, + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL + ) } - if (verifyCrossUser) { - runEachTestCaseWrapped(callingUserId, notCallingUserId) { - assertThrows(SecurityException::class.java, it) + val target = params.construct(context) + + fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) { + // Legacy makes no distinction by UID + val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID + if (throws) { + allUids.forEach { + assertFails { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + } else { + allUids.forEach { + runMethod(target, it, visible = true, callingUserId, targetUserId) + } + } + + // Legacy doesn't use QUERY_ALL, so the invisible package should always fail + allUids.forEach { + assertFails { + runMethod(target, it, visible = false, callingUserId, targetUserId) + } } } + val callingUserId = 0 + val notCallingUserId = 1 + + runTestCases(callingUserId, callingUserId, throws = false) + runTestCases(callingUserId, notCallingUserId, throws = true) + + // Legacy requires the _FULL permission, so this should continue to fail allowInteractAcrossUsers.set(true) + runTestCases(callingUserId, callingUserId, throws = false) + runTestCases(callingUserId, notCallingUserId, throws = true) + + allowInteractAcrossUsersFull.set(true) + runTestCases(callingUserId, callingUserId, throws = false) + runTestCases(callingUserId, notCallingUserId, throws = false) + } - runEachTestCaseWrapped(callingUserId, callingUserId) { - assertThrows(SecurityException::class.java, it) + private fun Context.initPermission(boolean: AtomicBoolean, permission: String) { + whenever(enforcePermission(eq(permission), anyInt(), anyInt(), anyString())) { + if (!boolean.get()) { + throw SecurityException() + } } - if (verifyCrossUser) { - runEachTestCaseWrapped(callingUserId, notCallingUserId) { - assertThrows(SecurityException::class.java, it) + whenever(checkPermission(eq(permission), anyInt(), anyInt())) { + if (boolean.get()) { + PackageManager.PERMISSION_GRANTED + } else { + PackageManager.PERMISSION_DENIED } } } - private fun runMethod(target: Any, callingUid: Int, callingUserId: Int = 0, userId: Int = 0) { - params.runMethod(target, callingUid, callingUserId, userId, proxy) + private fun runMethod( + target: Any, + callingUid: Int, + visible: Boolean = true, + callingUserId: Int = 0, + userId: Int = 0 + ): Any? { + val packageName = if (visible) VISIBLE_PKG else INVISIBLE_PKG + val uuid = if (visible) VISIBLE_UUID else INVISIBLE_UUID + return params.runMethod(target, callingUid, callingUserId, userId, packageName, uuid, proxy) + } + + private fun assertFails(block: () -> Any?) { + try { + val value = block() + // Some methods return false rather than throwing, so check that as well + if ((value as? Boolean) != false) { + // Can also return default value if it's a legacy call + if ((value as? Int) + != PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED + ) { + throw AssertionError("Expected call to return false, was $value") + } + } + } catch (e: SecurityException) { + } catch (e: PackageManager.NameNotFoundException) { + } catch (e: DomainVerificationManager.InvalidDomainSetException) { + // Any of these 3 exceptions are considered failures, which is expected + } } enum class Type { @@ -473,6 +687,12 @@ class DomainVerificationEnforcerTest { SELECTOR, // Holding the user setting permission, but targeting cross user - SELECTOR_USER + SELECTOR_USER, + + // Legacy required no permissions except when cross-user + LEGACY_QUERENT, + + // Holding the legacy preferred apps permission + LEGACY_SELECTOR } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt index 5792e02e37a2..5629d1c1107d 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt @@ -267,5 +267,8 @@ class DomainVerificationSettingsMutationTest { whenever(getPackageLocked(TEST_PKG)) { mockPkg() } whenever(schedule(anyInt(), any())) whenever(scheduleWriteSettings()) + + // This doesn't check for visibility; that's done in the enforcer test + whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false } } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java index 0311920477a1..059744388197 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/FakeLocationPowerSaveModeHelper.java @@ -19,6 +19,8 @@ package com.android.server.location.injector; import android.os.IPowerManager; import android.os.PowerManager.LocationPowerSaveMode; +import com.android.server.location.eventlog.LocationEventLog; + /** * Version of LocationPowerSaveModeHelper for testing. Power save mode is initialized as "no * change". diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java index c39a77eac23f..6156ba9359cd 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/SystemLocationPowerSaveModeHelperTest.java @@ -43,6 +43,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.LocalServices; +import com.android.server.location.eventlog.LocationEventLog; import com.android.server.location.injector.LocationPowerSaveModeHelper.LocationPowerSaveModeChangedListener; import org.junit.After; diff --git a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java index 8e5b16e1ee3a..2822d5c69091 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/injector/TestInjector.java @@ -16,9 +16,10 @@ package com.android.server.location.injector; +import com.android.server.location.eventlog.LocationEventLog; + public class TestInjector implements Injector { - private final LocationEventLog mLocationEventLog; private final FakeUserInfoHelper mUserInfoHelper; private final FakeAlarmHelper mAlarmHelper; private final FakeAppOpsHelper mAppOpsHelper; @@ -32,14 +33,17 @@ public class TestInjector implements Injector { private final LocationUsageLogger mLocationUsageLogger; public TestInjector() { - mLocationEventLog = new LocationEventLog(); + this(new LocationEventLog()); + } + + public TestInjector(LocationEventLog eventLog) { mUserInfoHelper = new FakeUserInfoHelper(); mAlarmHelper = new FakeAlarmHelper(); mAppOpsHelper = new FakeAppOpsHelper(); mLocationPermissionsHelper = new FakeLocationPermissionsHelper(mAppOpsHelper); mSettingsHelper = new FakeSettingsHelper(); mAppForegroundHelper = new FakeAppForegroundHelper(); - mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(mLocationEventLog); + mLocationPowerSaveModeHelper = new FakeLocationPowerSaveModeHelper(eventLog); mScreenInteractiveHelper = new FakeScreenInteractiveHelper(); mLocationAttributionHelper = new LocationAttributionHelper(mAppOpsHelper); mEmergencyHelper = new FakeEmergencyHelper(); @@ -100,9 +104,4 @@ public class TestInjector implements Injector { public LocationUsageLogger getLocationUsageLogger() { return mLocationUsageLogger; } - - @Override - public LocationEventLog getLocationEventLog() { - return mLocationEventLog; - } } diff --git a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java index 54fa89a1e24b..66b037d70a40 100644 --- a/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/location/provider/LocationProviderManagerTest.java @@ -83,6 +83,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.FgThread; import com.android.server.LocalServices; +import com.android.server.location.eventlog.LocationEventLog; import com.android.server.location.injector.FakeUserInfoHelper; import com.android.server.location.injector.TestInjector; @@ -159,17 +160,19 @@ public class LocationProviderManagerTest { doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class); doReturn(mWakeLock).when(mPowerManager).newWakeLock(anyInt(), anyString()); - mInjector = new TestInjector(); + LocationEventLog eventLog = new LocationEventLog(); + + mInjector = new TestInjector(eventLog); mInjector.getUserInfoHelper().startUser(OTHER_USER); - mPassive = new PassiveLocationProviderManager(mContext, mInjector); + mPassive = new PassiveLocationProviderManager(mContext, mInjector, eventLog); mPassive.startManager(); mPassive.setRealProvider(new PassiveLocationProvider(mContext)); mProvider = new TestProvider(PROPERTIES, IDENTITY); mProvider.setProviderAllowed(true); - mManager = new LocationProviderManager(mContext, mInjector, NAME, mPassive); + mManager = new LocationProviderManager(mContext, mInjector, eventLog, NAME, mPassive); mManager.startManager(); mManager.setRealProvider(mProvider); } diff --git a/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java index 50e7a0395a2a..58d6dae1637a 100644 --- a/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java +++ b/services/tests/servicestests/src/com/android/server/EntropyMixerTest.java @@ -34,7 +34,7 @@ public class EntropyMixerTest extends AndroidTestCase { assertEquals(0, FileUtils.readTextFile(file, 0, null).length()); // The constructor has the side effect of writing to file - new EntropyMixer(getContext(), "/dev/null", file.getCanonicalPath(), "/dev/null"); + new EntropyMixer(getContext(), "/dev/null", file.getCanonicalPath()); assertTrue(FileUtils.readTextFile(file, 0, null).length() > 0); } diff --git a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java index caa46da306fa..d039a9d6bfe1 100644 --- a/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java +++ b/services/tests/servicestests/src/com/android/server/app/GameManagerServiceTests.java @@ -18,14 +18,23 @@ package com.android.server.app; import static org.junit.Assert.assertEquals; +import android.Manifest; import android.app.GameManager; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.pm.PackageManager; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; + +import java.util.HashMap; +import java.util.function.Supplier; @RunWith(AndroidJUnit4.class) @SmallTest @@ -37,15 +46,88 @@ public class GameManagerServiceTests { private static final int USER_ID_1 = 1001; private static final int USER_ID_2 = 1002; + // Stolen from ConnectivityServiceTest.MockContext + class MockContext extends ContextWrapper { + private static final String TAG = "MockContext"; + + // Map of permission name -> PermissionManager.Permission_{GRANTED|DENIED} constant + private final HashMap<String, Integer> mMockedPermissions = new HashMap<>(); + + MockContext(Context base) { + super(base); + } + + /** + * Mock checks for the specified permission, and have them behave as per {@code granted}. + * + * <p>Passing null reverts to default behavior, which does a real permission check on the + * test package. + * @param granted One of {@link PackageManager#PERMISSION_GRANTED} or + * {@link PackageManager#PERMISSION_DENIED}. + */ + public void setPermission(String permission, Integer granted) { + mMockedPermissions.put(permission, granted); + } + + private int checkMockedPermission(String permission, Supplier<Integer> ifAbsent) { + final Integer granted = mMockedPermissions.get(permission); + return granted != null ? granted : ifAbsent.get(); + } + + @Override + public int checkPermission(String permission, int pid, int uid) { + return checkMockedPermission( + permission, () -> super.checkPermission(permission, pid, uid)); + } + + @Override + public int checkCallingOrSelfPermission(String permission) { + return checkMockedPermission( + permission, () -> super.checkCallingOrSelfPermission(permission)); + } + + @Override + public void enforceCallingOrSelfPermission(String permission, String message) { + final Integer granted = mMockedPermissions.get(permission); + if (granted == null) { + super.enforceCallingOrSelfPermission(permission, message); + return; + } + + if (!granted.equals(PackageManager.PERMISSION_GRANTED)) { + throw new SecurityException("[Test] permission denied: " + permission); + } + } + } + + @Mock + private MockContext mMockContext; + + @Before + public void setUp() throws Exception { + mMockContext = new MockContext(InstrumentationRegistry.getContext()); + } + + private void mockModifyGameModeGranted() { + mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE, + PackageManager.PERMISSION_GRANTED); + } + + private void mockModifyGameModeDenied() { + mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE, + PackageManager.PERMISSION_DENIED); + } + /** * By default game mode is not supported. */ @Test public void testGameModeDefaultValue() { - GameManagerService gameManagerService = - new GameManagerService(InstrumentationRegistry.getContext()); + GameManagerService gameManagerService = new GameManagerService(mMockContext); gameManagerService.onUserStarting(USER_ID_1); + mockModifyGameModeGranted(); + assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameManagerService.getGameMode(PACKAGE_NAME_0, USER_ID_1)); } @@ -55,10 +137,11 @@ public class GameManagerServiceTests { */ @Test public void testDefaultValueForNonexistentUser() { - GameManagerService gameManagerService = - new GameManagerService(InstrumentationRegistry.getContext()); + GameManagerService gameManagerService = new GameManagerService(mMockContext); gameManagerService.onUserStarting(USER_ID_1); + mockModifyGameModeGranted(); + gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_2); assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_2)); @@ -69,10 +152,11 @@ public class GameManagerServiceTests { */ @Test public void testGameMode() { - GameManagerService gameManagerService = - new GameManagerService(InstrumentationRegistry.getContext()); + GameManagerService gameManagerService = new GameManagerService(mMockContext); gameManagerService.onUserStarting(USER_ID_1); + mockModifyGameModeGranted(); + assertEquals(GameManager.GAME_MODE_UNSUPPORTED, gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1)); gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1); @@ -83,4 +167,48 @@ public class GameManagerServiceTests { assertEquals(GameManager.GAME_MODE_PERFORMANCE, gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1)); } + + /** + * Test permission.MANAGE_GAME_MODE is checked + */ + @Test + public void testGetGameModePermissionDenied() { + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + + // Update the game mode so we can read back something valid. + mockModifyGameModeGranted(); + gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1); + assertEquals(GameManager.GAME_MODE_STANDARD, + gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1)); + + // Deny permission.MANAGE_GAME_MODE and verify we get back GameManager.GAME_MODE_UNSUPPORTED + mockModifyGameModeDenied(); + assertEquals(GameManager.GAME_MODE_UNSUPPORTED, + gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1)); + } + + /** + * Test permission.MANAGE_GAME_MODE is checked + */ + @Test + public void testSetGameModePermissionDenied() { + GameManagerService gameManagerService = new GameManagerService(mMockContext); + gameManagerService.onUserStarting(USER_ID_1); + + // Update the game mode so we can read back something valid. + mockModifyGameModeGranted(); + gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_STANDARD, USER_ID_1); + assertEquals(GameManager.GAME_MODE_STANDARD, + gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1)); + + // Deny permission.MANAGE_GAME_MODE and verify the game mode is not updated. + mockModifyGameModeDenied(); + gameManagerService.setGameMode(PACKAGE_NAME_1, GameManager.GAME_MODE_PERFORMANCE, + USER_ID_1); + + mockModifyGameModeGranted(); + assertEquals(GameManager.GAME_MODE_STANDARD, + gameManagerService.getGameMode(PACKAGE_NAME_1, USER_ID_1)); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java index e6a238a775c6..ba6011140cf1 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java @@ -814,6 +814,7 @@ public class PackageParserTest { assertArrayEquals(a.splitSourceDirs, that.splitSourceDirs); assertArrayEquals(a.splitPublicSourceDirs, that.splitPublicSourceDirs); assertArrayEquals(a.resourceDirs, that.resourceDirs); + assertArrayEquals(a.overlayPaths, that.overlayPaths); assertEquals(a.seInfo, that.seInfo); assertArrayEquals(a.sharedLibraryFiles, that.sharedLibraryFiles); assertEquals(a.dataDir, that.dataDir); diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java index 938e4cc84e62..7297622b523f 100644 --- a/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/PackageUserStateTest.java @@ -21,11 +21,14 @@ import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATIO import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import android.content.pm.PackageManager; import android.content.pm.PackageUserState; import android.content.pm.SuspendDialogInfo; +import android.content.pm.overlay.OverlayPaths; import android.os.PersistableBundle; import android.util.ArrayMap; import android.util.ArraySet; @@ -346,4 +349,40 @@ public class PackageUserStateTest { assertLastPackageUsageSet( testState6, PackageManager.NOTIFY_PACKAGE_USE_REASONS_COUNT - 1, 60L); } + + @Test + public void testOverlayPaths() { + final PackageUserState testState = new PackageUserState(); + assertFalse(testState.setOverlayPaths(null)); + assertFalse(testState.setOverlayPaths(new OverlayPaths.Builder().build())); + + assertTrue(testState.setOverlayPaths(new OverlayPaths.Builder() + .addApkPath("/path/to/some.apk").build())); + assertFalse(testState.setOverlayPaths(new OverlayPaths.Builder() + .addApkPath("/path/to/some.apk").build())); + + assertTrue(testState.setOverlayPaths(new OverlayPaths.Builder().build())); + assertFalse(testState.setOverlayPaths(null)); + } + @Test + public void testSharedLibOverlayPaths() { + final PackageUserState testState = new PackageUserState(); + final String LIB_ONE = "lib1"; + final String LIB_TW0 = "lib2"; + assertFalse(testState.setSharedLibraryOverlayPaths(LIB_ONE, null)); + assertFalse(testState.setSharedLibraryOverlayPaths(LIB_ONE, + new OverlayPaths.Builder().build())); + + assertTrue(testState.setSharedLibraryOverlayPaths(LIB_ONE, new OverlayPaths.Builder() + .addApkPath("/path/to/some.apk").build())); + assertFalse(testState.setSharedLibraryOverlayPaths(LIB_ONE, new OverlayPaths.Builder() + .addApkPath("/path/to/some.apk").build())); + assertTrue(testState.setSharedLibraryOverlayPaths(LIB_TW0, new OverlayPaths.Builder() + .addApkPath("/path/to/some.apk").build())); + + assertTrue(testState.setSharedLibraryOverlayPaths(LIB_ONE, + new OverlayPaths.Builder().build())); + assertFalse(testState.setSharedLibraryOverlayPaths(LIB_ONE, null)); + } + } diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt index 61252c473530..ff0aec71a0df 100644 --- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt +++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt @@ -248,6 +248,7 @@ open class AndroidPackageParsingTestBase { .ignored("Deferred pre-R, but assigned immediately in R")} requiresSmallestWidthDp=${this.requiresSmallestWidthDp} resourceDirs=${this.resourceDirs?.contentToString()} + overlayPaths=${this.overlayPaths?.contentToString()} roundIconRes=${this.roundIconRes} scanPublicSourceDir=${this.scanPublicSourceDir .ignored("Deferred pre-R, but assigned immediately in R")} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java index b3116d970725..17c941179853 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAssistantsTest.java @@ -15,6 +15,9 @@ */ package com.android.server.notification; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertTrue; + import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Matchers.any; @@ -138,6 +141,30 @@ public class NotificationAssistantsTest extends UiServiceTestCase { } @Test + public void testXmlMigratingAllowedAdjustments() throws Exception { + // Old tag, need migration + String xml = "<q_allowed_adjustments types=\"adj_1\"/>"; + + TypedXmlPullParser parser = Xml.newFastPullParser(); + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + parser.nextTag(); + mAssistants.readExtraTag("q_allowed_adjustments", parser); + assertTrue(mAssistants.isAdjustmentAllowed("adj_1")); + assertEquals(mNm.DEFAULT_ALLOWED_ADJUSTMENTS.length + 1, + mAssistants.getAllowedAssistantAdjustments().size()); + + // New TAG + xml = "<s_allowed_adjustments types=\"adj_2\"/>"; + parser.setInput(new BufferedInputStream( + new ByteArrayInputStream(xml.toString().getBytes())), null); + parser.nextTag(); + mAssistants.readExtraTag("s_allowed_adjustments", parser); + assertTrue(mAssistants.isAdjustmentAllowed("adj_2")); + assertEquals(1, mAssistants.getAllowedAssistantAdjustments().size()); + } + + @Test public void testSetPackageOrComponentEnabled_onlyOnePackage() throws Exception { ComponentName component1 = ComponentName.unflattenFromString("package/Component1"); ComponentName component2 = ComponentName.unflattenFromString("package/Component2"); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 0cb1255c6830..9e419d4c3f3b 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -1526,15 +1526,19 @@ public class UsageStatsService extends SystemService implements @Override public ParceledListSlice<UsageStats> queryUsageStats(int bucketType, long beginTime, - long endTime, String callingPackage) { + long endTime, String callingPackage, int userId) { if (!hasPermission(callingPackage)) { return null; } + final int callingUid = Binder.getCallingUid(); + userId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), callingUid, + userId, false, true, "queryUsageStats", callingPackage); + + // Check the caller's userId for obfuscation decision, not the user being queried final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller( - Binder.getCallingUid(), UserHandle.getCallingUserId()); + callingUid, UserHandle.getCallingUserId()); - final int userId = UserHandle.getCallingUserId(); final long token = Binder.clearCallingIdentity(); try { final List<UsageStats> results = UsageStatsService.this.queryUsageStats( diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index ac584c175c59..21cf3e57115d 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -4748,6 +4748,21 @@ public class CarrierConfigManager { public static final String KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL = "network_temp_not_metered_supported_bool"; + /* + * Boolean indicating whether the SIM PIN can be stored and verified + * seamlessly after an unattended reboot. + * + * The device configuration value {@code config_allow_pin_storage_for_unattended_reboot} + * ultimately controls whether this carrier configuration option is used. Where + * {@code config_allow_pin_storage_for_unattended_reboot} is false, the value of the + * {@link #KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL} carrier configuration option is + * ignored. + * + * @hide + */ + public static final String KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL = + "store_sim_pin_for_unattended_reboot_bool"; + /** The default value for every variable. */ private final static PersistableBundle sDefaults; @@ -5304,6 +5319,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_USE_ACS_FOR_RCS_BOOL, false); sDefaults.putBoolean(KEY_NETWORK_TEMP_NOT_METERED_SUPPORTED_BOOL, true); sDefaults.putInt(KEY_DEFAULT_RTT_MODE_INT, 0); + sDefaults.putBoolean(KEY_STORE_SIM_PIN_FOR_UNATTENDED_REBOOT_BOOL, true); } /** diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 65b8de2b2216..441446cdc48d 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -14994,6 +14994,7 @@ public class TelephonyManager { } return THERMAL_MITIGATION_RESULT_UNKNOWN_ERROR; } + /** * Registers a listener object to receive notification of changes * in specified telephony states. @@ -15350,4 +15351,66 @@ public class TelephonyManager { return PhoneCapability.DEFAULT_SSSS_CAPABILITY; } } + + /** + * The unattended reboot was prepared successfully. + * @hide + */ + @SystemApi + public static final int PREPARE_UNATTENDED_REBOOT_SUCCESS = 0; + + /** + * The unattended reboot was prepared, but the user will need to manually + * enter the PIN code of at least one SIM card present in the device. + * @hide + */ + @SystemApi + public static final int PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED = 1; + + /** + * The unattended reboot was not prepared due to generic error. + * @hide + */ + @SystemApi + public static final int PREPARE_UNATTENDED_REBOOT_ERROR = 2; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"PREPARE_UNATTENDED_REBOOT_"}, + value = { + PREPARE_UNATTENDED_REBOOT_SUCCESS, + PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED, + PREPARE_UNATTENDED_REBOOT_ERROR + }) + public @interface PrepareUnattendedRebootResult {} + + /** + * Prepare TelephonyManager for an unattended reboot. The reboot is required to be done + * shortly (e.g. within 15 seconds) after the API is invoked. + * + * <p>Requires Permission: + * {@link android.Manifest.permission#REBOOT} + * + * @return {@link #PREPARE_UNATTENDED_REBOOT_SUCCESS} in case of success. + * {@link #PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED} if the device contains + * at least one SIM card for which the user needs to manually enter the PIN + * code after the reboot. {@link #PREPARE_UNATTENDED_REBOOT_ERROR} in case + * of error. + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.REBOOT) + @PrepareUnattendedRebootResult + public int prepareForUnattendedReboot() { + try { + ITelephony service = getITelephony(); + if (service != null) { + return service.prepareForUnattendedReboot(); + } + } catch (RemoteException e) { + Log.e(TAG, "Telephony#prepareForUnattendedReboot RemoteException", e); + e.rethrowFromSystemServer(); + } + return PREPARE_UNATTENDED_REBOOT_ERROR; + } } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 0cd17da3c0c5..1d049530ba77 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -2387,4 +2387,18 @@ interface ITelephony { * Gets the current phone capability. */ PhoneCapability getPhoneCapability(); + + /** + * Prepare TelephonyManager for an unattended reboot. The reboot is + * required to be done shortly after the API is invoked. + * + * Requires system privileges. + * + * @return {@link #PREPARE_UNATTENDED_REBOOT_SUCCESS} in case of success. + * {@link #PREPARE_UNATTENDED_REBOOT_PIN_REQUIRED} if the device contains + * at least one SIM card for which the user needs to manually enter the PIN + * code after the reboot. {@link #PREPARE_UNATTENDED_REBOOT_ERROR} in case + * of error. + */ + int prepareForUnattendedReboot(); } diff --git a/tests/ApkVerityTest/OWNERS b/tests/ApkVerityTest/OWNERS new file mode 100644 index 000000000000..d67285ede44a --- /dev/null +++ b/tests/ApkVerityTest/OWNERS @@ -0,0 +1,3 @@ +# Bug component: 36824 + +victorhsieh@google.com diff --git a/tools/hiddenapi/Android.bp b/tools/hiddenapi/Android.bp deleted file mode 100644 index e0eb06cbea7f..000000000000 --- a/tools/hiddenapi/Android.bp +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -python_binary_host { - name: "merge_csv", - main: "merge_csv.py", - srcs: ["merge_csv.py"], - version: { - py2: { - enabled: false, - }, - py3: { - enabled: true, - embedded_launcher: true - }, - }, -} diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py deleted file mode 100755 index 28ff606d0381..000000000000 --- a/tools/hiddenapi/generate_hiddenapi_lists.py +++ /dev/null @@ -1,383 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2018 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Generate API lists for non-SDK API enforcement.""" -import argparse -from collections import defaultdict, namedtuple -import functools -import os -import re -import sys - -# Names of flags recognized by the `hiddenapi` tool. -FLAG_SDK = 'sdk' -FLAG_UNSUPPORTED = 'unsupported' -FLAG_BLOCKED = 'blocked' -FLAG_MAX_TARGET_O = 'max-target-o' -FLAG_MAX_TARGET_P = 'max-target-p' -FLAG_MAX_TARGET_Q = 'max-target-q' -FLAG_MAX_TARGET_R = 'max-target-r' -FLAG_CORE_PLATFORM_API = 'core-platform-api' -FLAG_PUBLIC_API = 'public-api' -FLAG_SYSTEM_API = 'system-api' -FLAG_TEST_API = 'test-api' - -# List of all known flags. -FLAGS_API_LIST = [ - FLAG_SDK, - FLAG_UNSUPPORTED, - FLAG_BLOCKED, - FLAG_MAX_TARGET_O, - FLAG_MAX_TARGET_P, - FLAG_MAX_TARGET_Q, - FLAG_MAX_TARGET_R, -] -ALL_FLAGS = FLAGS_API_LIST + [ - FLAG_CORE_PLATFORM_API, - FLAG_PUBLIC_API, - FLAG_SYSTEM_API, - FLAG_TEST_API, -] - -FLAGS_API_LIST_SET = set(FLAGS_API_LIST) -ALL_FLAGS_SET = set(ALL_FLAGS) - -# Option specified after one of FLAGS_API_LIST to indicate that -# only known and otherwise unassigned entries should be assign the -# given flag. -# For example, the max-target-P list is checked in as it was in P, -# but signatures have changes since then. The flag instructs this -# script to skip any entries which do not exist any more. -FLAG_IGNORE_CONFLICTS = "ignore-conflicts" - -# Option specified after one of FLAGS_API_LIST to express that all -# apis within a given set of packages should be assign the given flag. -FLAG_PACKAGES = "packages" - -# Option specified after one of FLAGS_API_LIST to indicate an extra -# tag that should be added to the matching APIs. -FLAG_TAG = "tag" - -# Regex patterns of fields/methods used in serialization. These are -# considered public API despite being hidden. -SERIALIZATION_PATTERNS = [ - r'readObject\(Ljava/io/ObjectInputStream;\)V', - r'readObjectNoData\(\)V', - r'readResolve\(\)Ljava/lang/Object;', - r'serialVersionUID:J', - r'serialPersistentFields:\[Ljava/io/ObjectStreamField;', - r'writeObject\(Ljava/io/ObjectOutputStream;\)V', - r'writeReplace\(\)Ljava/lang/Object;', -] - -# Single regex used to match serialization API. It combines all the -# SERIALIZATION_PATTERNS into a single regular expression. -SERIALIZATION_REGEX = re.compile(r'.*->(' + '|'.join(SERIALIZATION_PATTERNS) + r')$') - -# Predicates to be used with filter_apis. -HAS_NO_API_LIST_ASSIGNED = lambda api, flags: not FLAGS_API_LIST_SET.intersection(flags) -IS_SERIALIZATION = lambda api, flags: SERIALIZATION_REGEX.match(api) - - -class StoreOrderedOptions(argparse.Action): - """An argparse action that stores a number of option arguments in the order that - they were specified. - """ - def __call__(self, parser, args, values, option_string = None): - items = getattr(args, self.dest, None) - if items is None: - items = [] - items.append([option_string.lstrip('-'), values]) - setattr(args, self.dest, items) - -def get_args(): - """Parses command line arguments. - - Returns: - Namespace: dictionary of parsed arguments - """ - parser = argparse.ArgumentParser() - parser.add_argument('--output', required=True) - parser.add_argument('--csv', nargs='*', default=[], metavar='CSV_FILE', - help='CSV files to be merged into output') - - for flag in ALL_FLAGS: - parser.add_argument('--' + flag, dest='ordered_flags', metavar='TXT_FILE', - action=StoreOrderedOptions, help='lists of entries with flag "' + flag + '"') - parser.add_argument('--' + FLAG_IGNORE_CONFLICTS, dest='ordered_flags', nargs=0, - action=StoreOrderedOptions, help='Indicates that only known and otherwise unassigned ' - 'entries should be assign the given flag. Must follow a list of entries and applies ' - 'to the preceding such list.') - parser.add_argument('--' + FLAG_PACKAGES, dest='ordered_flags', nargs=0, - action=StoreOrderedOptions, help='Indicates that the previous list of entries ' - 'is a list of packages. All members in those packages will be given the flag. ' - 'Must follow a list of entries and applies to the preceding such list.') - parser.add_argument('--' + FLAG_TAG, dest='ordered_flags', nargs=1, - action=StoreOrderedOptions, help='Adds an extra tag to the previous list of entries. ' - 'Must follow a list of entries and applies to the preceding such list.') - - return parser.parse_args() - - -def read_lines(filename): - """Reads entire file and return it as a list of lines. - - Lines which begin with a hash are ignored. - - Args: - filename (string): Path to the file to read from. - - Returns: - Lines of the file as a list of string. - """ - with open(filename, 'r') as f: - lines = f.readlines(); - lines = filter(lambda line: not line.startswith('#'), lines) - lines = map(lambda line: line.strip(), lines) - return set(lines) - - -def write_lines(filename, lines): - """Writes list of lines into a file, overwriting the file if it exists. - - Args: - filename (string): Path to the file to be writting into. - lines (list): List of strings to write into the file. - """ - lines = map(lambda line: line + '\n', lines) - with open(filename, 'w') as f: - f.writelines(lines) - - -def extract_package(signature): - """Extracts the package from a signature. - - Args: - signature (string): JNI signature of a method or field. - - Returns: - The package name of the class containing the field/method. - """ - full_class_name = signature.split(";->")[0] - # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy - if (full_class_name[0] != "L"): - raise ValueError("Expected to start with 'L': %s" % full_class_name) - full_class_name = full_class_name[1:] - # If full_class_name doesn't contain '/', then package_name will be ''. - package_name = full_class_name.rpartition("/")[0] - return package_name.replace('/', '.') - - -class FlagsDict: - def __init__(self): - self._dict_keyset = set() - self._dict = defaultdict(set) - - def _check_entries_set(self, keys_subset, source): - assert isinstance(keys_subset, set) - assert keys_subset.issubset(self._dict_keyset), ( - "Error: {} specifies signatures not present in code:\n" - "{}" - "Please visit go/hiddenapi for more information.").format( - source, "".join(map(lambda x: " " + str(x) + "\n", keys_subset - self._dict_keyset))) - - def _check_flags_set(self, flags_subset, source): - assert isinstance(flags_subset, set) - assert flags_subset.issubset(ALL_FLAGS_SET), ( - "Error processing: {}\n" - "The following flags were not recognized: \n" - "{}\n" - "Please visit go/hiddenapi for more information.").format( - source, "\n".join(flags_subset - ALL_FLAGS_SET)) - - def filter_apis(self, filter_fn): - """Returns APIs which match a given predicate. - - This is a helper function which allows to filter on both signatures (keys) and - flags (values). The built-in filter() invokes the lambda only with dict's keys. - - Args: - filter_fn : Function which takes two arguments (signature/flags) and returns a boolean. - - Returns: - A set of APIs which match the predicate. - """ - return set(filter(lambda x: filter_fn(x, self._dict[x]), self._dict_keyset)) - - def get_valid_subset_of_unassigned_apis(self, api_subset): - """Sanitizes a key set input to only include keys which exist in the dictionary - and have not been assigned any API list flags. - - Args: - entries_subset (set/list): Key set to be sanitized. - - Returns: - Sanitized key set. - """ - assert isinstance(api_subset, set) - return api_subset.intersection(self.filter_apis(HAS_NO_API_LIST_ASSIGNED)) - - def generate_csv(self): - """Constructs CSV entries from a dictionary. - - Old versions of flags are used to generate the file. - - Returns: - List of lines comprising a CSV file. See "parse_and_merge_csv" for format description. - """ - lines = [] - for api in self._dict: - flags = sorted(self._dict[api]) - lines.append(",".join([api] + flags)) - return sorted(lines) - - def parse_and_merge_csv(self, csv_lines, source = "<unknown>"): - """Parses CSV entries and merges them into a given dictionary. - - The expected CSV format is: - <api signature>,<flag1>,<flag2>,...,<flagN> - - Args: - csv_lines (list of strings): Lines read from a CSV file. - source (string): Origin of `csv_lines`. Will be printed in error messages. - - Throws: - AssertionError if parsed flags are invalid. - """ - # Split CSV lines into arrays of values. - csv_values = [ line.split(',') for line in csv_lines ] - - # Update the full set of API signatures. - self._dict_keyset.update([ csv[0] for csv in csv_values ]) - - # Check that all flags are known. - csv_flags = set() - for csv in csv_values: - csv_flags.update(csv[1:]) - self._check_flags_set(csv_flags, source) - - # Iterate over all CSV lines, find entry in dict and append flags to it. - for csv in csv_values: - flags = csv[1:] - if (FLAG_PUBLIC_API in flags) or (FLAG_SYSTEM_API in flags): - flags.append(FLAG_SDK) - self._dict[csv[0]].update(flags) - - def assign_flag(self, flag, apis, source="<unknown>", tag = None): - """Assigns a flag to given subset of entries. - - Args: - flag (string): One of ALL_FLAGS. - apis (set): Subset of APIs to receive the flag. - source (string): Origin of `entries_subset`. Will be printed in error messages. - - Throws: - AssertionError if parsed API signatures of flags are invalid. - """ - # Check that all APIs exist in the dict. - self._check_entries_set(apis, source) - - # Check that the flag is known. - self._check_flags_set(set([ flag ]), source) - - # Iterate over the API subset, find each entry in dict and assign the flag to it. - for api in apis: - self._dict[api].add(flag) - if tag: - self._dict[api].add(tag) - - -FlagFile = namedtuple('FlagFile', ('flag', 'file', 'ignore_conflicts', 'packages', 'tag')) - -def parse_ordered_flags(ordered_flags): - r = [] - currentflag, file, ignore_conflicts, packages, tag = None, None, False, False, None - for flag_value in ordered_flags: - flag, value = flag_value[0], flag_value[1] - if flag in ALL_FLAGS_SET: - if currentflag: - r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag)) - ignore_conflicts, packages, tag = False, False, None - currentflag = flag - file = value - else: - if currentflag is None: - raise argparse.ArgumentError('--%s is only allowed after one of %s' % ( - flag, ' '.join(['--%s' % f for f in ALL_FLAGS_SET]))) - if flag == FLAG_IGNORE_CONFLICTS: - ignore_conflicts = True - elif flag == FLAG_PACKAGES: - packages = True - elif flag == FLAG_TAG: - tag = value[0] - - - if currentflag: - r.append(FlagFile(currentflag, file, ignore_conflicts, packages, tag)) - return r - - -def main(argv): - # Parse arguments. - args = vars(get_args()) - flagfiles = parse_ordered_flags(args['ordered_flags']) - - # Initialize API->flags dictionary. - flags = FlagsDict() - - # Merge input CSV files into the dictionary. - # Do this first because CSV files produced by parsing API stubs will - # contain the full set of APIs. Subsequent additions from text files - # will be able to detect invalid entries, and/or filter all as-yet - # unassigned entries. - for filename in args["csv"]: - flags.parse_and_merge_csv(read_lines(filename), filename) - - # Combine inputs which do not require any particular order. - # (1) Assign serialization API to SDK. - flags.assign_flag(FLAG_SDK, flags.filter_apis(IS_SERIALIZATION)) - - # (2) Merge text files with a known flag into the dictionary. - for info in flagfiles: - if (not info.ignore_conflicts) and (not info.packages): - flags.assign_flag(info.flag, read_lines(info.file), info.file, info.tag) - - # Merge text files where conflicts should be ignored. - # This will only assign the given flag if: - # (a) the entry exists, and - # (b) it has not been assigned any other flag. - # Because of (b), this must run after all strict assignments have been performed. - for info in flagfiles: - if info.ignore_conflicts: - valid_entries = flags.get_valid_subset_of_unassigned_apis(read_lines(info.file)) - flags.assign_flag(info.flag, valid_entries, filename, info.tag) - - # All members in the specified packages will be assigned the appropriate flag. - for info in flagfiles: - if info.packages: - packages_needing_list = set(read_lines(info.file)) - should_add_signature_to_list = lambda sig,lists: extract_package( - sig) in packages_needing_list and not lists - valid_entries = flags.filter_apis(should_add_signature_to_list) - flags.assign_flag(info.flag, valid_entries, info.file, info.tag) - - # Mark all remaining entries as blocked. - flags.assign_flag(FLAG_BLOCKED, flags.filter_apis(HAS_NO_API_LIST_ASSIGNED)) - - # Write output. - write_lines(args["output"], flags.generate_csv()) - -if __name__ == "__main__": - main(sys.argv) diff --git a/tools/hiddenapi/generate_hiddenapi_lists_test.py b/tools/hiddenapi/generate_hiddenapi_lists_test.py deleted file mode 100755 index 82d117fbbbab..000000000000 --- a/tools/hiddenapi/generate_hiddenapi_lists_test.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2018 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the 'License'); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an 'AS IS' BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Unit tests for Hidden API list generation.""" -import unittest -from generate_hiddenapi_lists import * - -class TestHiddenapiListGeneration(unittest.TestCase): - - def test_filter_apis(self): - # Initialize flags so that A and B are put on the whitelist and - # C, D, E are left unassigned. Try filtering for the unassigned ones. - flags = FlagsDict() - flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B,' + FLAG_SDK, - 'C', 'D', 'E']) - filter_set = flags.filter_apis(lambda api, flags: not flags) - self.assertTrue(isinstance(filter_set, set)) - self.assertEqual(filter_set, set([ 'C', 'D', 'E' ])) - - def test_get_valid_subset_of_unassigned_keys(self): - # Create flags where only A is unassigned. - flags = FlagsDict() - flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B', 'C']) - flags.assign_flag(FLAG_UNSUPPORTED, set(['C'])) - self.assertEqual(flags.generate_csv(), - [ 'A,' + FLAG_SDK, 'B', 'C,' + FLAG_UNSUPPORTED ]) - - # Check three things: - # (1) B is selected as valid unassigned - # (2) A is not selected because it is assigned 'whitelist' - # (3) D is not selected because it is not a valid key - self.assertEqual( - flags.get_valid_subset_of_unassigned_apis(set(['A', 'B', 'D'])), set([ 'B' ])) - - def test_parse_and_merge_csv(self): - flags = FlagsDict() - - # Test empty CSV entry. - self.assertEqual(flags.generate_csv(), []) - - # Test new additions. - flags.parse_and_merge_csv([ - 'A,' + FLAG_UNSUPPORTED, - 'B,' + FLAG_BLOCKED + ',' + FLAG_MAX_TARGET_O, - 'C,' + FLAG_SDK + ',' + FLAG_SYSTEM_API, - 'D,' + FLAG_UNSUPPORTED + ',' + FLAG_TEST_API, - 'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API, - ]) - self.assertEqual(flags.generate_csv(), [ - 'A,' + FLAG_UNSUPPORTED, - 'B,' + FLAG_BLOCKED + "," + FLAG_MAX_TARGET_O, - 'C,' + FLAG_SYSTEM_API + ',' + FLAG_SDK, - 'D,' + FLAG_UNSUPPORTED + ',' + FLAG_TEST_API, - 'E,' + FLAG_BLOCKED + ',' + FLAG_TEST_API, - ]) - - # Test unknown flag. - with self.assertRaises(AssertionError): - flags.parse_and_merge_csv([ 'Z,foo' ]) - - def test_assign_flag(self): - flags = FlagsDict() - flags.parse_and_merge_csv(['A,' + FLAG_SDK, 'B']) - - # Test new additions. - flags.assign_flag(FLAG_UNSUPPORTED, set([ 'A', 'B' ])) - self.assertEqual(flags.generate_csv(), - [ 'A,' + FLAG_UNSUPPORTED + "," + FLAG_SDK, 'B,' + FLAG_UNSUPPORTED ]) - - # Test invalid API signature. - with self.assertRaises(AssertionError): - flags.assign_flag(FLAG_SDK, set([ 'C' ])) - - # Test invalid flag. - with self.assertRaises(AssertionError): - flags.assign_flag('foo', set([ 'A' ])) - - def test_extract_package(self): - signature = 'Lcom/foo/bar/Baz;->method1()Lcom/bar/Baz;' - expected_package = 'com.foo.bar' - self.assertEqual(extract_package(signature), expected_package) - - signature = 'Lcom/foo1/bar/MyClass;->method2()V' - expected_package = 'com.foo1.bar' - self.assertEqual(extract_package(signature), expected_package) - - signature = 'Lcom/foo_bar/baz/MyClass;->method3()V' - expected_package = 'com.foo_bar.baz' - self.assertEqual(extract_package(signature), expected_package) - -if __name__ == '__main__': - unittest.main() diff --git a/tools/hiddenapi/merge_csv.py b/tools/hiddenapi/merge_csv.py deleted file mode 100755 index 6a5b0e1347b2..000000000000 --- a/tools/hiddenapi/merge_csv.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python -# -# Copyright (C) 2018 The Android Open Source Project -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Merge multiple CSV files, possibly with different columns. -""" - -import argparse -import csv -import io - -from zipfile import ZipFile - -args_parser = argparse.ArgumentParser(description='Merge given CSV files into a single one.') -args_parser.add_argument('--header', help='Comma separated field names; ' - 'if missing determines the header from input files.') -args_parser.add_argument('--zip_input', help='ZIP archive with all CSV files to merge.') -args_parser.add_argument('--output', help='Output file for merged CSV.', - default='-', type=argparse.FileType('w')) -args_parser.add_argument('files', nargs=argparse.REMAINDER) -args = args_parser.parse_args() - - -def dict_reader(input): - return csv.DictReader(input, delimiter=',', quotechar='|') - - -if args.zip_input and len(args.files) > 0: - raise ValueError('Expecting either a single ZIP with CSV files' - ' or a list of CSV files as input; not both.') - -csv_readers = [] -if len(args.files) > 0: - for file in args.files: - csv_readers.append(dict_reader(open(file, 'r'))) -elif args.zip_input: - with ZipFile(args.zip_input) as zip: - for entry in zip.namelist(): - if entry.endswith('.uau'): - csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r')))) - -headers = set() -if args.header: - fieldnames = args.header.split(',') -else: - # Build union of all columns from source files: - for reader in csv_readers: - headers = headers.union(reader.fieldnames) - fieldnames = sorted(headers) - -# Concatenate all files to output: -writer = csv.DictWriter(args.output, delimiter=',', quotechar='|', quoting=csv.QUOTE_MINIMAL, - dialect='unix', fieldnames=fieldnames) -writer.writeheader() -for reader in csv_readers: - for row in reader: - writer.writerow(row) |