diff options
| author | 2024-08-08 11:39:46 -0700 | |
|---|---|---|
| committer | 2024-08-08 11:39:46 -0700 | |
| commit | e7e6ec868a3277517795e77976186fc0dfed9365 (patch) | |
| tree | 30e55d3bbab99ac3d30c9ea8b97bce9b67c59f1b | |
| parent | 2008d30e49a986a4cfc863b46b7ec668c7735603 (diff) | |
| parent | a3ec1eab222414913d2696bf076e899bf6bf93ff (diff) | |
Merge 24Q3 (ab/AP3A.240905.001) to aosp-main-future
Bug: 347831320
Merged-In: Ib851a4cb2332902f4c96eec5131eb7d5aff99dd5
Change-Id: Ieba5f32b7f917c795a9e18cc53b86da0bb5fa2fa
201 files changed, 3175 insertions, 1363 deletions
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 2313fa27afe1..5214d2c9c02a 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2981,9 +2981,7 @@ public class AppOpsManager { new AppOpInfo.Builder(OP_ESTABLISH_VPN_MANAGER, OPSTR_ESTABLISH_VPN_MANAGER, "ESTABLISH_VPN_MANAGER").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), new AppOpInfo.Builder(OP_ACCESS_RESTRICTED_SETTINGS, OPSTR_ACCESS_RESTRICTED_SETTINGS, - "ACCESS_RESTRICTED_SETTINGS").setDefaultMode( - android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() - ? MODE_DEFAULT : MODE_ALLOWED) + "ACCESS_RESTRICTED_SETTINGS").setDefaultMode(AppOpsManager.MODE_ALLOWED) .setDisableReset(true).setRestrictRead(true).build(), new AppOpInfo.Builder(OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO, "RECEIVE_SOUNDTRIGGER_AUDIO").setDefaultMode(AppOpsManager.MODE_ALLOWED) diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 370aff8e7a0f..b51462e6b363 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -30,6 +30,7 @@ import android.content.res.AssetManager; import android.content.res.CompatResources; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.Flags; import android.content.res.Resources; import android.content.res.ResourcesImpl; import android.content.res.ResourcesKey; @@ -138,16 +139,22 @@ public class ResourcesManager { private final ArrayMap<String, SharedLibraryAssets> mSharedLibAssetsMap = new ArrayMap<>(); + @VisibleForTesting + public ArrayMap<String, SharedLibraryAssets> getRegisteredResourcePaths() { + return mSharedLibAssetsMap; + } + /** * The internal function to register the resources paths of a package (e.g. a shared library). * This will collect the package resources' paths from its ApplicationInfo and add them to all * existing and future contexts while the application is running. */ public void registerResourcePaths(@NonNull String uniqueId, @NonNull ApplicationInfo appInfo) { - SharedLibraryAssets sharedLibAssets = new SharedLibraryAssets(appInfo.sourceDir, - appInfo.splitSourceDirs, appInfo.sharedLibraryFiles, - appInfo.resourceDirs, appInfo.overlayPaths); + if (!Flags.registerResourcePaths()) { + return; + } + final var sharedLibAssets = new SharedLibraryAssets(appInfo); synchronized (mLock) { if (mSharedLibAssetsMap.containsKey(uniqueId)) { Slog.v(TAG, "Package resources' paths for uniqueId: " + uniqueId @@ -155,18 +162,37 @@ public class ResourcesManager { return; } mSharedLibAssetsMap.put(uniqueId, sharedLibAssets); - appendLibAssetsLocked(sharedLibAssets.getAllAssetPaths()); - Slog.v(TAG, "The following resources' paths have been added: " - + Arrays.toString(sharedLibAssets.getAllAssetPaths())); + appendLibAssetsLocked(sharedLibAssets); + Slog.v(TAG, "The following library key has been added: " + + sharedLibAssets.getResourcesKey()); } } - private static class ApkKey { + /** + * Apply the registered library paths to the passed impl object + * @return the hash code for the current version of the registered paths + */ + public int updateResourceImplWithRegisteredLibs(@NonNull ResourcesImpl impl) { + if (!Flags.registerResourcePaths()) { + return 0; + } + + final var collector = new PathCollector(null); + final int size = mSharedLibAssetsMap.size(); + for (int i = 0; i < size; i++) { + final var libraryKey = mSharedLibAssetsMap.valueAt(i).getResourcesKey(); + collector.appendKey(libraryKey); + } + impl.getAssets().addPresetApkKeys(extractApkKeys(collector.collectedKey())); + return size; + } + + public static class ApkKey { public final String path; public final boolean sharedLib; public final boolean overlay; - ApkKey(String path, boolean sharedLib, boolean overlay) { + public ApkKey(String path, boolean sharedLib, boolean overlay) { this.path = path; this.sharedLib = sharedLib; this.overlay = overlay; @@ -190,6 +216,12 @@ public class ResourcesManager { return this.path.equals(other.path) && this.sharedLib == other.sharedLib && this.overlay == other.overlay; } + + @Override + public String toString() { + return "ApkKey[" + (sharedLib ? "lib" : "app") + (overlay ? ", overlay" : "") + ": " + + path + "]"; + } } /** @@ -505,7 +537,10 @@ public class ResourcesManager { return "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap"; } - private @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException { + /** + * Loads the ApkAssets object for the passed key, or picks the one from the cache if available. + */ + public @NonNull ApkAssets loadApkAssets(@NonNull final ApkKey key) throws IOException { ApkAssets apkAssets; // Optimistically check if this ApkAssets exists somewhere else. @@ -747,8 +782,8 @@ public class ResourcesManager { private @Nullable ResourcesImpl findOrCreateResourcesImplForKeyLocked( @NonNull ResourcesKey key, @Nullable ApkAssetsSupplier apkSupplier) { ResourcesImpl impl = findResourcesImplForKeyLocked(key); - // ResourcesImpl also need to be recreated if its shared library count is not up-to-date. - if (impl == null || impl.getSharedLibCount() != mSharedLibAssetsMap.size()) { + // ResourcesImpl also need to be recreated if its shared library hash is not up-to-date. + if (impl == null || impl.getAppliedSharedLibsHash() != mSharedLibAssetsMap.size()) { impl = createResourcesImpl(key, apkSupplier); if (impl != null) { mResourceImpls.put(key, new WeakReference<>(impl)); @@ -1533,56 +1568,109 @@ public class ResourcesManager { } } - private void appendLibAssetsLocked(String[] libAssets) { - synchronized (mLock) { - // Record which ResourcesImpl need updating - // (and what ResourcesKey they should update to). - final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); + /** + * A utility class to collect resources paths into a ResourcesKey object: + * - Separates the libraries and the overlays into different sets as those are loaded in + * different ways. + * - Allows to start with an existing original key object, and copies all non-path related + * properties into the final one. + * - Preserves the path order while dropping all duplicates in an efficient manner. + */ + private static class PathCollector { + public final ResourcesKey originalKey; + public final ArrayList<String> orderedLibs = new ArrayList<>(); + public final ArraySet<String> libsSet = new ArraySet<>(); + public final ArrayList<String> orderedOverlays = new ArrayList<>(); + public final ArraySet<String> overlaysSet = new ArraySet<>(); + + static void appendNewPath(@NonNull String path, + @NonNull ArraySet<String> uniquePaths, @NonNull ArrayList<String> orderedPaths) { + if (uniquePaths.add(path)) { + orderedPaths.add(path); + } + } - final int implCount = mResourceImpls.size(); - for (int i = 0; i < implCount; i++) { - final ResourcesKey key = mResourceImpls.keyAt(i); - final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); - final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; - if (impl == null) { - Slog.w(TAG, "Found a ResourcesImpl which is null, skip it and continue to " - + "append shared library assets for next ResourcesImpl."); - continue; - } + static void appendAllNewPaths(@Nullable String[] paths, + @NonNull ArraySet<String> uniquePaths, @NonNull ArrayList<String> orderedPaths) { + if (paths == null) return; + for (int i = 0, size = paths.length; i < size; i++) { + appendNewPath(paths[i], uniquePaths, orderedPaths); + } + } - var newDirs = new ArrayList<String>(); - var dirsSet = new ArraySet<String>(); - if (key.mLibDirs != null) { - final int dirsLength = key.mLibDirs.length; - for (int k = 0; k < dirsLength; k++) { - newDirs.add(key.mLibDirs[k]); - dirsSet.add(key.mLibDirs[k]); - } - } - final int assetsLength = libAssets.length; - for (int j = 0; j < assetsLength; j++) { - if (dirsSet.add(libAssets[j])) { - newDirs.add(libAssets[j]); - } - } - String[] newLibAssets = newDirs.toArray(new String[0]); - if (!Arrays.equals(newLibAssets, key.mLibDirs)) { - updatedResourceKeys.put(impl, new ResourcesKey( - key.mResDir, - key.mSplitResDirs, - key.mOverlayPaths, - newLibAssets, - key.mDisplayId, - key.mOverrideConfiguration, - key.mCompatInfo, - key.mLoaders)); - } + PathCollector(@Nullable ResourcesKey original) { + originalKey = original; + if (originalKey != null) { + appendKey(originalKey); } + } - redirectAllResourcesToNewImplLocked(updatedResourceKeys); + public void appendKey(@NonNull ResourcesKey key) { + appendAllNewPaths(key.mLibDirs, libsSet, orderedLibs); + appendAllNewPaths(key.mOverlayPaths, overlaysSet, orderedOverlays); + } + + boolean isSameAsOriginal() { + if (originalKey == null) { + return orderedLibs.isEmpty() && orderedOverlays.isEmpty(); + } + return ((originalKey.mLibDirs == null && orderedLibs.isEmpty()) + || (originalKey.mLibDirs != null + && originalKey.mLibDirs.length == orderedLibs.size())) + && ((originalKey.mOverlayPaths == null && orderedOverlays.isEmpty()) + || (originalKey.mOverlayPaths != null + && originalKey.mOverlayPaths.length == orderedOverlays.size())); + } + + @NonNull ResourcesKey collectedKey() { + return new ResourcesKey( + originalKey == null ? null : originalKey.mResDir, + originalKey == null ? null : originalKey.mSplitResDirs, + orderedOverlays.toArray(new String[0]), orderedLibs.toArray(new String[0]), + originalKey == null ? 0 : originalKey.mDisplayId, + originalKey == null ? null : originalKey.mOverrideConfiguration, + originalKey == null ? null : originalKey.mCompatInfo, + originalKey == null ? null : originalKey.mLoaders); } } + /** + * Takes the original resources key and the one containing a set of library paths and overlays + * to append, and combines them together. In case when the original key already contains all + * those paths this function returns null, otherwise it makes a new ResourcesKey object. + */ + private @Nullable ResourcesKey createNewResourceKeyIfNeeded( + @NonNull ResourcesKey original, @NonNull ResourcesKey library) { + final var collector = new PathCollector(original); + collector.appendKey(library); + return collector.isSameAsOriginal() ? null : collector.collectedKey(); + } + + /** + * Append the newly registered shared library asset paths to all existing resources objects. + */ + private void appendLibAssetsLocked(@NonNull SharedLibraryAssets libAssets) { + // Record the ResourcesImpl's that need updating, and what ResourcesKey they should + // update to. + final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys = new ArrayMap<>(); + final int implCount = mResourceImpls.size(); + for (int i = 0; i < implCount; i++) { + final ResourcesKey key = mResourceImpls.keyAt(i); + final WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i); + final ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null; + if (impl == null) { + Slog.w(TAG, "Found a null ResourcesImpl, skipped."); + continue; + } + + final var newKey = createNewResourceKeyIfNeeded(key, libAssets.getResourcesKey()); + if (newKey != null) { + updatedResourceKeys.put(impl, newKey); + } + } + redirectAllResourcesToNewImplLocked(updatedResourceKeys); + } + private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs, @NonNull final ApplicationInfo appInfo) { try { @@ -1718,8 +1806,9 @@ public class ResourcesManager { } } - // Another redirect function which will loop through all Resources and reload ResourcesImpl - // if it needs a shared library asset paths update. + // Another redirect function which will loop through all Resources in the process, even the ones + // the app created outside of the regular Android Runtime, and reload their ResourcesImpl if it + // needs a shared library asset paths update. private void redirectAllResourcesToNewImplLocked( @NonNull final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceKeys) { cleanupReferences(mAllResourceReferences, mAllResourceReferencesQueue); @@ -1830,52 +1919,35 @@ public class ResourcesManager { } } - public static class SharedLibraryAssets{ - private final String[] mAssetPaths; - - SharedLibraryAssets(String sourceDir, String[] splitSourceDirs, String[] sharedLibraryFiles, - String[] resourceDirs, String[] overlayPaths) { - mAssetPaths = collectAssetPaths(sourceDir, splitSourceDirs, sharedLibraryFiles, - resourceDirs, overlayPaths); - } - - private @NonNull String[] collectAssetPaths(String sourceDir, String[] splitSourceDirs, - String[] sharedLibraryFiles, String[] resourceDirs, String[] overlayPaths) { - final String[][] inputLists = { - splitSourceDirs, sharedLibraryFiles, resourceDirs, overlayPaths - }; - - final ArraySet<String> assetPathSet = new ArraySet<>(); - final List<String> assetPathList = new ArrayList<>(); - if (sourceDir != null) { - assetPathSet.add(sourceDir); - assetPathList.add(sourceDir); - } - - for (int i = 0; i < inputLists.length; i++) { - if (inputLists[i] != null) { - for (int j = 0; j < inputLists[i].length; j++) { - if (assetPathSet.add(inputLists[i][j])) { - assetPathList.add(inputLists[i][j]); - } - } - } - } - return assetPathList.toArray(new String[0]); + @VisibleForTesting + public static class SharedLibraryAssets { + private final ResourcesKey mResourcesKey; + + private SharedLibraryAssets(ApplicationInfo appInfo) { + // We're loading all library's files as shared libs, regardless where they are in + // its own ApplicationInfo. + final var collector = new PathCollector(null); + PathCollector.appendNewPath(appInfo.sourceDir, collector.libsSet, + collector.orderedLibs); + PathCollector.appendAllNewPaths(appInfo.splitSourceDirs, collector.libsSet, + collector.orderedLibs); + PathCollector.appendAllNewPaths(appInfo.sharedLibraryFiles, collector.libsSet, + collector.orderedLibs); + PathCollector.appendAllNewPaths(appInfo.resourceDirs, collector.overlaysSet, + collector.orderedOverlays); + PathCollector.appendAllNewPaths(appInfo.overlayPaths, collector.overlaysSet, + collector.orderedOverlays); + mResourcesKey = collector.collectedKey(); } /** - * @return all the asset paths of this collected in this class. + * @return the resources key for this library assets. */ - public @NonNull String[] getAllAssetPaths() { - return mAssetPaths; + public @NonNull ResourcesKey getResourcesKey() { + return mResourcesKey; } } - public @NonNull ArrayMap<String, SharedLibraryAssets> getSharedLibAssetsMap() { - return new ArrayMap<>(mSharedLibAssetsMap); - } - /** * Add all resources references to the list which is designed to help to append shared library * asset paths. This is invoked in Resources constructor to include all Resources instances. diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 273e40a21bb2..fd9361d4f2d8 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -17,6 +17,7 @@ package android.content.res; import static android.content.res.Resources.ID_NULL; +import static android.app.ResourcesManager.ApkKey; import android.annotation.AnyRes; import android.annotation.ArrayRes; @@ -26,12 +27,14 @@ import android.annotation.Nullable; import android.annotation.StringRes; import android.annotation.StyleRes; import android.annotation.TestApi; +import android.app.ResourcesManager; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.res.Configuration.NativeConfig; import android.content.res.loader.ResourcesLoader; import android.os.Build; import android.os.ParcelFileDescriptor; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; import android.util.SparseArray; @@ -264,7 +267,7 @@ public final class AssetManager implements AutoCloseable { } sSystemApkAssetsSet = new ArraySet<>(apkAssets); - sSystemApkAssets = apkAssets.toArray(new ApkAssets[apkAssets.size()]); + sSystemApkAssets = apkAssets.toArray(new ApkAssets[0]); if (sSystem == null) { sSystem = new AssetManager(true /*sentinel*/); } @@ -448,7 +451,7 @@ public final class AssetManager implements AutoCloseable { @Deprecated @UnsupportedAppUsage public int addAssetPath(String path) { - return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/); + return addAssetPathInternal(List.of(new ApkKey(path, false, false)), false); } /** @@ -458,7 +461,7 @@ public final class AssetManager implements AutoCloseable { @Deprecated @UnsupportedAppUsage public int addAssetPathAsSharedLibrary(String path) { - return addAssetPathInternal(path, false /*overlay*/, true /*appAsLib*/); + return addAssetPathInternal(List.of(new ApkKey(path, true, false)), false); } /** @@ -468,55 +471,110 @@ public final class AssetManager implements AutoCloseable { @Deprecated @UnsupportedAppUsage public int addOverlayPath(String path) { - return addAssetPathInternal(path, true /*overlay*/, false /*appAsLib*/); + return addAssetPathInternal(List.of(new ApkKey(path, false, true)), false); } /** * @hide */ - public void addSharedLibraryPaths(@NonNull String[] paths) { - final int length = paths.length; - for (int i = 0; i < length; i++) { - addAssetPathInternal(paths[i], false, true); - } + public void addPresetApkKeys(@NonNull List<ApkKey> keys) { + addAssetPathInternal(keys, true); } - private int addAssetPathInternal(String path, boolean overlay, boolean appAsLib) { - Objects.requireNonNull(path, "path"); + private int addAssetPathInternal(List<ApkKey> apkKeys, boolean presetAssets) { + Objects.requireNonNull(apkKeys, "apkKeys"); + if (apkKeys.isEmpty()) { + return 0; + } + synchronized (this) { ensureOpenLocked(); final int count = mApkAssets.length; - // See if we already have it loaded. - for (int i = 0; i < count; i++) { - if (mApkAssets[i].getAssetPath().equals(path)) { - return i + 1; - } + // See if we already have some of the apkKeys loaded. + final int originalAssetsCount = mApkAssets.length; + + // Getting an assets' path is a relatively expensive operation, cache them. + final ArrayMap<String, Integer> assetPaths = new ArrayMap<>(originalAssetsCount); + for (int i = 0; i < originalAssetsCount; i++) { + assetPaths.put(mApkAssets[i].getAssetPath(), i); } - final ApkAssets assets; - try { - if (overlay) { - // TODO(b/70343104): This hardcoded path will be removed once - // addAssetPathInternal is deleted. - final String idmapPath = "/data/resource-cache/" - + path.substring(1).replace('/', '@') - + "@idmap"; - assets = ApkAssets.loadOverlayFromPath(idmapPath, 0 /* flags */); + final var newKeys = new ArrayList<ApkKey>(apkKeys.size()); + int lastFoundIndex = -1; + for (int i = 0, pathsSize = apkKeys.size(); i < pathsSize; i++) { + final var key = apkKeys.get(i); + final var index = assetPaths.get(key.path); + if (index == null) { + newKeys.add(key); } else { - assets = ApkAssets.loadFromPath(path, - appAsLib ? ApkAssets.PROPERTY_DYNAMIC : 0); + lastFoundIndex = index; } - } catch (IOException e) { - return 0; + } + if (newKeys.isEmpty()) { + return lastFoundIndex + 1; } - mApkAssets = Arrays.copyOf(mApkAssets, count + 1); - mApkAssets[count] = assets; - nativeSetApkAssets(mObject, mApkAssets, true, false); + final var newAssets = loadAssets(newKeys); + if (newAssets.isEmpty()) { + return 0; + } + mApkAssets = makeNewAssetsArrayLocked(newAssets); + nativeSetApkAssets(mObject, mApkAssets, true, presetAssets); invalidateCachesLocked(-1); - return count + 1; + return originalAssetsCount + 1; + } + } + + /** + * Insert the new assets preserving the correct order: all non-loader assets go before all + * of the loader assets. + */ + @GuardedBy("this") + private @NonNull ApkAssets[] makeNewAssetsArrayLocked( + @NonNull ArrayList<ApkAssets> newNonLoaderAssets) { + final int originalAssetsCount = mApkAssets.length; + int firstLoaderIndex = originalAssetsCount; + for (int i = 0; i < originalAssetsCount; i++) { + if (mApkAssets[i].isForLoader()) { + firstLoaderIndex = i; + break; + } + } + final int newAssetsSize = newNonLoaderAssets.size(); + final var newAssetsArray = new ApkAssets[originalAssetsCount + newAssetsSize]; + if (firstLoaderIndex > 0) { + // This should always be true, but who knows... + System.arraycopy(mApkAssets, 0, newAssetsArray, 0, firstLoaderIndex); + } + for (int i = 0; i < newAssetsSize; i++) { + newAssetsArray[firstLoaderIndex + i] = newNonLoaderAssets.get(i); + } + if (originalAssetsCount > firstLoaderIndex) { + System.arraycopy( + mApkAssets, firstLoaderIndex, + newAssetsArray, firstLoaderIndex + newAssetsSize, + originalAssetsCount - firstLoaderIndex); + } + return newAssetsArray; + } + + private static @NonNull ArrayList<ApkAssets> loadAssets(@NonNull ArrayList<ApkKey> keys) { + final int pathsSize = keys.size(); + final var loadedAssets = new ArrayList<ApkAssets>(pathsSize); + final var resourcesManager = ResourcesManager.getInstance(); + for (int i = 0; i < pathsSize; i++) { + final var key = keys.get(i); + try { + // ResourcesManager has a cache of loaded assets, ensuring we don't open the same + // file repeatedly, which is useful for the common overlays and registered + // shared libraries. + loadedAssets.add(resourcesManager.loadApkAssets(key)); + } catch (IOException e) { + Log.w(TAG, "Failed to load asset, key = " + key, e); + } } + return loadedAssets; } /** @hide */ diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java index 31cacb705d0e..c33eaeae1c45 100644 --- a/core/java/android/content/res/ResourcesImpl.java +++ b/core/java/android/content/res/ResourcesImpl.java @@ -29,7 +29,6 @@ import android.annotation.StyleRes; import android.annotation.StyleableRes; import android.app.LocaleConfig; import android.app.ResourcesManager; -import android.app.ResourcesManager.SharedLibraryAssets; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.Config; @@ -48,7 +47,6 @@ import android.os.Build; import android.os.LocaleList; import android.os.ParcelFileDescriptor; import android.os.Trace; -import android.util.ArrayMap; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -147,10 +145,9 @@ public class ResourcesImpl { // Cyclical cache used for recently-accessed XML files. private int mLastCachedXmlBlockIndex = -1; - // The number of shared libraries registered within this ResourcesImpl, which is designed to - // help to determine whether this ResourcesImpl is outdated on shared library information and - // needs to be replaced. - private int mSharedLibCount; + // The hash that allows to detect when the shared libraries applied to this object have changed, + // and it is outdated and needs to be replaced. + private final int mAppliedSharedLibsHash; private final int[] mCachedXmlBlockCookies = new int[XML_BLOCK_CACHE_SIZE]; private final String[] mCachedXmlBlockFiles = new String[XML_BLOCK_CACHE_SIZE]; private final XmlBlock[] mCachedXmlBlocks = new XmlBlock[XML_BLOCK_CACHE_SIZE]; @@ -204,15 +201,8 @@ public class ResourcesImpl { public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics, @Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) { mAssets = assets; - if (Flags.registerResourcePaths()) { - ArrayMap<String, SharedLibraryAssets> sharedLibMap = - ResourcesManager.getInstance().getSharedLibAssetsMap(); - final int size = sharedLibMap.size(); - for (int i = 0; i < size; i++) { - assets.addSharedLibraryPaths(sharedLibMap.valueAt(i).getAllAssetPaths()); - } - mSharedLibCount = sharedLibMap.size(); - } + mAppliedSharedLibsHash = + ResourcesManager.getInstance().updateResourceImplWithRegisteredLibs(this); mMetrics.setToDefaults(); mDisplayAdjustments = displayAdjustments; mConfiguration.setToDefaults(); @@ -1615,7 +1605,7 @@ public class ResourcesImpl { } } - public int getSharedLibCount() { - return mSharedLibCount; + public int getAppliedSharedLibsHash() { + return mAppliedSharedLibsHash; } -} +}
\ No newline at end of file diff --git a/core/java/android/credentials/selection/IntentFactory.java b/core/java/android/credentials/selection/IntentFactory.java index b98a0d825227..c521b96fd8ee 100644 --- a/core/java/android/credentials/selection/IntentFactory.java +++ b/core/java/android/credentials/selection/IntentFactory.java @@ -232,7 +232,17 @@ public class IntentFactory { oemComponentName, PackageManager.ComponentInfoFlags.of( PackageManager.MATCH_SYSTEM_ONLY)); - if (info.enabled && info.exported) { + boolean oemComponentEnabled = info.enabled; + int runtimeComponentEnabledState = context.getPackageManager() + .getComponentEnabledSetting(oemComponentName); + if (runtimeComponentEnabledState == PackageManager + .COMPONENT_ENABLED_STATE_ENABLED) { + oemComponentEnabled = true; + } else if (runtimeComponentEnabledState == PackageManager + .COMPONENT_ENABLED_STATE_DISABLED) { + oemComponentEnabled = false; + } + if (oemComponentEnabled && info.exported) { intentResultBuilder.setOemUiUsageStatus(IntentCreationResult .OemUiUsageStatus.SUCCESS); Slog.i(TAG, diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index 6fffb822c9ca..056ca93159dd 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -564,7 +564,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, then this list will only contain * CONTROL_ZOOM_RATIO_RANGE and SCALER_AVAILABLE_MAX_DIGITAL_ZOOM * - * @see INFO_SESSION_CONFIGURATION_QUERY_VERSION + * @see #INFO_SESSION_CONFIGURATION_QUERY_VERSION */ @NonNull @FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY) diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 6968f279dbc0..fbed50ad3c2d 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -57,7 +57,7 @@ import java.util.Set; * * <p>CaptureRequests can be created by using a {@link Builder} instance, * obtained by calling {@link CameraDevice#createCaptureRequest} or {@link - * CameraManager#createCaptureRequest}</p> + * CameraDevice.CameraDeviceSetup#createCaptureRequest}</p> * * <p>CaptureRequests are given to {@link CameraCaptureSession#capture} or * {@link CameraCaptureSession#setRepeatingRequest} to capture images from a camera.</p> @@ -84,7 +84,7 @@ import java.util.Set; * @see CameraCaptureSession#setRepeatingBurst * @see CameraDevice#createCaptureRequest * @see CameraDevice#createReprocessCaptureRequest - * @see CameraManager#createCaptureRequest + * @see CameraDevice.CameraDeviceSetup#createCaptureRequest */ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> implements Parcelable { @@ -812,8 +812,9 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * A builder for capture requests. * * <p>To obtain a builder instance, use the - * {@link CameraDevice#createCaptureRequest} or {@link CameraManager#createCaptureRequest} - * method, which initializes the request fields to one of the templates defined in + * {@link CameraDevice#createCaptureRequest} or + * {@link CameraDevice.CameraDeviceSetup#createCaptureRequest} method, which + * initializes the request fields to one of the templates defined in * {@link CameraDevice}. * * @see CameraDevice#createCaptureRequest @@ -822,7 +823,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * @see CameraDevice#TEMPLATE_STILL_CAPTURE * @see CameraDevice#TEMPLATE_VIDEO_SNAPSHOT * @see CameraDevice#TEMPLATE_MANUAL - * @see CameraManager#createCaptureRequest + * @see CameraDevice.CameraDeviceSetup#createCaptureRequest */ public final static class Builder { diff --git a/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java b/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java index 69a6e9b0ab32..bf3f59fc7480 100644 --- a/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/ExtensionSessionConfiguration.java @@ -23,12 +23,13 @@ import android.graphics.ColorSpace; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraExtensionCharacteristics.Extension; import android.hardware.camera2.CameraExtensionSession; +import android.media.ImageReader; + +import com.android.internal.camera.flags.Flags; import java.util.List; import java.util.concurrent.Executor; -import com.android.internal.camera.flags.Flags; - /** * A class that aggregates all supported arguments for * {@link CameraExtensionSession} initialization. diff --git a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java index f3b7b919d87d..fb2c2f036b42 100644 --- a/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java +++ b/core/java/android/hardware/camera2/params/MandatoryStreamCombination.java @@ -260,7 +260,7 @@ public final class MandatoryStreamCombination { * smaller sizes, then the resulting * {@link android.hardware.camera2.params.SessionConfiguration session configuration} can * be tested either by calling {@link CameraDevice#createCaptureSession} or - * {@link CameraDeviceSetup#isSessionConfigurationSupported}. + * {@link CameraDevice.CameraDeviceSetup#isSessionConfigurationSupported}. * * @return non-modifiable ascending list of available sizes. */ diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java index 3b2913c81d49..0c55ed5323a0 100644 --- a/core/java/android/hardware/camera2/params/SessionConfiguration.java +++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java @@ -130,8 +130,8 @@ public final class SessionConfiguration implements Parcelable { * Create a new {@link SessionConfiguration} with sessionType and output configurations. * * <p>The SessionConfiguration objects created by this constructor can be used by - * {@link CameraDeviceSetup.isSessionConfigurationSupported} and {@link - * CameraDeviceSetup.getSessionCharacteristics} to query a camera device's feature + * {@link CameraDeviceSetup#isSessionConfigurationSupported} and {@link + * CameraDeviceSetup#getSessionCharacteristics} to query a camera device's feature * combination support and session specific characteristics. For the SessionConfiguration * object to be used to create a capture session, {@link #setStateCallback} must be called to * specify the state callback function, and any incomplete OutputConfigurations must be diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java index 2ded615580fd..903e91646332 100644 --- a/core/java/android/hardware/fingerprint/FingerprintManager.java +++ b/core/java/android/hardware/fingerprint/FingerprintManager.java @@ -699,6 +699,25 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing } /** + * Set whether the HAL should ignore display touches. + * Only applies to sensors where the HAL is reponsible for handling touches. + * @hide + */ + @RequiresPermission(USE_BIOMETRIC_INTERNAL) + public void setIgnoreDisplayTouches(long requestId, int sensorId, boolean ignoreTouch) { + if (mService == null) { + Slog.w(TAG, "setIgnoreDisplayTouches: no fingerprint service"); + return; + } + + try { + mService.setIgnoreDisplayTouches(requestId, sensorId, ignoreTouch); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Request fingerprint enrollment. This call warms up the fingerprint hardware * and starts scanning for fingerprints. Progress will be indicated by callbacks to the * {@link EnrollmentCallback} object. It terminates when diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java index f701ec3ec367..d84d29292ed5 100644 --- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java +++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java @@ -120,6 +120,14 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna } /** + * Returns if sensor type is ultrasonic Udfps + * @return true if sensor is ultrasonic Udfps, false otherwise + */ + public boolean isUltrasonicUdfps() { + return sensorType == TYPE_UDFPS_ULTRASONIC; + } + + /** * Returns if sensor type is side-FPS * @return true if sensor is side-fps, false otherwise */ diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl index 742fa570bad7..370f097b6850 100644 --- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl +++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl @@ -195,6 +195,9 @@ interface IFingerprintService { @EnforcePermission("USE_BIOMETRIC_INTERNAL") void onUdfpsUiEvent(int event, long requestId, int sensorId); + @EnforcePermission("USE_BIOMETRIC_INTERNAL") + void setIgnoreDisplayTouches(long requestId, int sensorId, boolean ignoreTouches); + // Sets the controller for managing the UDFPS overlay. @EnforcePermission("USE_BIOMETRIC_INTERNAL") void setUdfpsOverlayController(in IUdfpsOverlayController controller); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d91508fe1612..5c392ae6bd2c 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -11066,6 +11066,13 @@ public final class Settings { "active_unlock_on_biometric_fail"; /** + * Whether or not active unlock triggers on legacy unlock intents. + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_UNLOCK_INTENT_LEGACY = + "active_unlock_on_unlock_intent_legacy"; + + /** * If active unlock triggers on biometric failures, include the following error codes * as a biometric failure. See {@link android.hardware.biometrics.BiometricFaceConstants}. * Error codes should be separated by a pipe. For example: "1|4|5". If active unlock diff --git a/core/java/android/service/chooser/flags.aconfig b/core/java/android/service/chooser/flags.aconfig index d6425c397bbb..ed20207cfb0b 100644 --- a/core/java/android/service/chooser/flags.aconfig +++ b/core/java/android/service/chooser/flags.aconfig @@ -32,3 +32,14 @@ flag { description: "Provides additional callbacks with information about user actions in ChooserResult" bug: "263474465" } + +flag { + name: "fix_resolver_memory_leak" + is_exported: true + namespace: "intentresolver" + description: "ResolverActivity memory leak (through the AppPredictor callback) fix" + bug: "346671041" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 83ff88bdcc1c..1b3b3ebbecfc 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -89,7 +89,7 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro SyncRtSurfaceTransactionApplier.SurfaceParams surfaceParams = params[i]; applyParams(t, surfaceParams, mTmpFloat9); } - t.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId()); + t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); t.apply(); t.close(); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 2377b869e804..dde4b34ea655 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -126,6 +126,7 @@ import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodCl import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import static com.android.window.flags.Flags.activityWindowInfoFlag; import static com.android.window.flags.Flags.enableBufferTransformHintFromDisplay; +import static com.android.window.flags.Flags.insetsControlChangedItem; import static com.android.window.flags.Flags.setScPropertiesInClient; import static com.android.window.flags.Flags.windowSessionRelayoutInfo; import static com.android.text.flags.Flags.disableHandwritingInitiatorForIme; @@ -1266,7 +1267,6 @@ public final class ViewRootImpl implements ViewParent, mDensity = context.getResources().getDisplayMetrics().densityDpi; mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi; mFallbackEventHandler = new PhoneFallbackEventHandler(context); - // TODO(b/222696368): remove getSfInstance usage and use vsyncId for transactions mChoreographer = Choreographer.getInstance(); mInsetsController = new InsetsController(new ViewRootInsetsControllerHost(this)); mImeBackAnimationController = new ImeBackAnimationController(this, mInsetsController); @@ -4405,7 +4405,7 @@ public final class ViewRootImpl implements ViewParent, if (mAppStartInfoTimestampsFlagValue && !mAppStartTrackingStarted) { mAppStartTrackingStarted = true; Transaction transaction = new Transaction(); - transaction.addTransactionCompletedListener(mExecutor, + transaction.addTransactionCompletedListener(mSimpleExecutor, new Consumer<TransactionStats>() { @Override public void accept(TransactionStats transactionStats) { @@ -11417,8 +11417,13 @@ public final class ViewRootImpl implements ViewParent, @Override public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl.Array activeControls) { - final boolean isFromInsetsControlChangeItem = mIsFromTransactionItem; - mIsFromTransactionItem = false; + final boolean isFromInsetsControlChangeItem; + if (insetsControlChangedItem()) { + isFromInsetsControlChangeItem = mIsFromTransactionItem; + mIsFromTransactionItem = false; + } else { + isFromInsetsControlChangeItem = false; + } final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor == null) { if (isFromInsetsControlChangeItem) { diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java index 8bd39fbe3a52..939ba29b71ad 100644 --- a/core/java/android/window/TransitionInfo.java +++ b/core/java/android/window/TransitionInfo.java @@ -205,7 +205,7 @@ public final class TransitionInfo implements Parcelable { FLAG_SYNC, FLAG_CONFIG_AT_END, FLAG_FIRST_CUSTOM - }) + }, flag = true) public @interface ChangeFlags {} private final @TransitionType int mType; diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index b71468247e37..985dc102b31b 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -192,4 +192,15 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + name: "ensure_wallpaper_in_transitions" + namespace: "windowing_frontend" + description: "Ensure that wallpaper window tokens are always present/available for collection in transitions" + bug: "347593088" + metadata { + purpose: PURPOSE_BUGFIX + } +} + diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 0a4762d91610..552cb7a1b978 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -191,3 +191,14 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + namespace: "windowing_sdk" + name: "fix_no_container_update_without_resize" + description: "Fix the containers not being updated when the Task is brought to front and has the same configuration" + bug: "344721335" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java index 920981e2b8fe..a1945352ae09 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -1209,9 +1209,19 @@ public class ResolverActivity extends Activity implements if (!isChangingConfigurations() && mPickOptionRequest != null) { mPickOptionRequest.cancel(); } - if (mMultiProfilePagerAdapter != null - && mMultiProfilePagerAdapter.getActiveListAdapter() != null) { - mMultiProfilePagerAdapter.getActiveListAdapter().onDestroy(); + if (mMultiProfilePagerAdapter != null) { + ResolverListAdapter activeAdapter = + mMultiProfilePagerAdapter.getActiveListAdapter(); + if (activeAdapter != null) { + activeAdapter.onDestroy(); + } + if (android.service.chooser.Flags.fixResolverMemoryLeak()) { + ResolverListAdapter inactiveAdapter = + mMultiProfilePagerAdapter.getInactiveListAdapter(); + if (inactiveAdapter != null) { + inactiveAdapter.onDestroy(); + } + } } } diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml index e983427129c8..0f4f219f7e24 100644 --- a/core/res/res/values/config_telephony.xml +++ b/core/res/res/values/config_telephony.xml @@ -393,6 +393,12 @@ <bool name="config_wait_for_device_alignment_in_demo_datagram">false</bool> <java-symbol type="bool" name="config_wait_for_device_alignment_in_demo_datagram" /> + <!-- Boolean indicating whether to enable MMS to be attempted on IWLAN if possible, even if + existing cellular networks already supports IWLAN. + --> + <bool name="force_iwlan_mms_feature_enabled">false</bool> + <java-symbol type="bool" name="force_iwlan_mms_feature_enabled" /> + <!-- The time duration in millis after which Telephony will abort the last message datagram sending requests. Telephony starts a timer when receiving a last message datagram sending request in either OFF, IDLE, or NOT_CONNECTED state. In NOT_CONNECTED, the duration of the diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 10373a59d819..1a2f0ccb06ba 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6503,17 +6503,17 @@ ul.</string> <!-- Fingerprint dangling notification title --> <string name="fingerprint_dangling_notification_title">Set up Fingerprint Unlock again</string> <!-- Fingerprint dangling notification content for only 1 fingerprint deleted --> - <string name="fingerprint_dangling_notification_msg_1"><xliff:g id="fingerprint">%s</xliff:g> wasn\'t working well and was deleted</string> + <string name="fingerprint_dangling_notification_msg_1"><xliff:g id="fingerprint">%s</xliff:g> can no longer be recognized.</string> <!-- Fingerprint dangling notification content for more than 1 fingerprints deleted --> - <string name="fingerprint_dangling_notification_msg_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> weren\'t working well and were deleted</string> + <string name="fingerprint_dangling_notification_msg_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> can no longer be recognized.</string> <!-- Fingerprint dangling notification content for only 1 fingerprint deleted and no fingerprint left--> - <string name="fingerprint_dangling_notification_msg_all_deleted_1"><xliff:g id="fingerprint">%s</xliff:g> wasn\'t working well and was deleted. Set it up again to unlock your phone with fingerprint.</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_1"><xliff:g id="fingerprint">%s</xliff:g> can no longer be recognized. Set up Fingerprint Unlock again.</string> <!-- Fingerprint dangling notification content for more than 1 fingerprints deleted and no fingerprint left --> - <string name="fingerprint_dangling_notification_msg_all_deleted_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> weren\'t working well and were deleted. Set them up again to unlock your phone with your fingerprint.</string> + <string name="fingerprint_dangling_notification_msg_all_deleted_2"><xliff:g id="fingerprint">%1$s</xliff:g> and <xliff:g id="fingerprint">%2$s</xliff:g> can no longer be recognized. Set up Fingerprint Unlock again.</string> <!-- Face dangling notification title --> <string name="face_dangling_notification_title">Set up Face Unlock again</string> <!-- Face dangling notification content --> - <string name="face_dangling_notification_msg">Your face model wasn\'t working well and was deleted. Set it up again to unlock your phone with face.</string> + <string name="face_dangling_notification_msg">Your face model can no longer be recognized. Set up Face Unlock again.</string> <!-- Biometric dangling notification "set up" action button --> <string name="biometric_dangling_notification_action_set_up">Set up</string> <!-- Biometric dangling notification "Not now" action button --> diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java index 6b3cf7bb628f..ee1b6589f7e6 100644 --- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java +++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java @@ -44,7 +44,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -382,8 +381,7 @@ public class ResourcesManagerTest extends TestCase { assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets)); // Package resources' paths should be cached in ResourcesManager. - assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance() - .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths())); + assertNotNull(ResourcesManager.getInstance().getRegisteredResourcePaths().get(TEST_LIB)); // Revert the ResourcesManager instance back. ResourcesManager.setInstance(oriResourcesManager); @@ -414,9 +412,7 @@ public class ResourcesManagerTest extends TestCase { assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets)); // Package resources' paths should be cached in ResourcesManager. - assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance() - .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths())); - + assertNotNull(ResourcesManager.getInstance().getRegisteredResourcePaths().get(TEST_LIB)); // Revert the ResourcesManager instance back. ResourcesManager.setInstance(oriResourcesManager); } @@ -452,9 +448,7 @@ public class ResourcesManagerTest extends TestCase { assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets)); // Package resources' paths should be cached in ResourcesManager. - assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance() - .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths())); - + assertNotNull(ResourcesManager.getInstance().getRegisteredResourcePaths().get(TEST_LIB)); // Revert the ResourcesManager instance back. ResourcesManager.setInstance(oriResourcesManager); } @@ -493,9 +487,7 @@ public class ResourcesManagerTest extends TestCase { assertTrue(allResourcePathsLoaded(resourcePaths, loadedAssets)); // Package resources' paths should be cached in ResourcesManager. - assertEquals(Arrays.toString(resourcePaths), Arrays.toString(ResourcesManager.getInstance() - .getSharedLibAssetsMap().get(TEST_LIB).getAllAssetPaths())); - + assertNotNull(ResourcesManager.getInstance().getRegisteredResourcePaths().get(TEST_LIB)); // Revert the ResourcesManager instance back. ResourcesManager.setInstance(oriResourcesManager); } diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index 66b47dae1b3e..a115c659d85a 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -40,6 +40,7 @@ <permission name="android.permission.MASTER_CLEAR"/> <permission name="android.permission.MEDIA_CONTENT_CONTROL"/> <permission name="android.permission.MODIFY_AUDIO_ROUTING" /> + <permission name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED" /> <permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/> <permission name="android.permission.MODIFY_PHONE_STATE"/> <permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index f78e2b5170fc..09625151fda4 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -899,14 +899,23 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen @GuardedBy("mLock") void updateContainersInTaskIfVisible(@NonNull WindowContainerTransaction wct, int taskId) { final TaskContainer taskContainer = getTaskContainer(taskId); - if (taskContainer != null && taskContainer.isVisible()) { + if (taskContainer == null) { + return; + } + + if (taskContainer.isVisible()) { updateContainersInTask(wct, taskContainer); + } else if (Flags.fixNoContainerUpdateWithoutResize()) { + // the TaskFragmentContainers need to be updated when the task becomes visible + taskContainer.mTaskFragmentContainersNeedsUpdate = true; } } @GuardedBy("mLock") private void updateContainersInTask(@NonNull WindowContainerTransaction wct, @NonNull TaskContainer taskContainer) { + taskContainer.mTaskFragmentContainersNeedsUpdate = false; + // Update all TaskFragments in the Task. Make a copy of the list since some may be // removed on updating. final List<TaskFragmentContainer> containers = taskContainer.getTaskFragmentContainers(); diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index ee00c4cd67eb..20ad53ee19a8 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -108,6 +108,12 @@ class TaskContainer { private boolean mPlaceholderRuleSuppressed; /** + * {@code true} if the TaskFragments in this Task needs to be updated next time the Task + * becomes visible. See {@link #shouldUpdateContainer(TaskFragmentParentInfo)} + */ + boolean mTaskFragmentContainersNeedsUpdate; + + /** * The {@link TaskContainer} constructor * * @param taskId The ID of the Task, which must match {@link Activity#getTaskId()} with @@ -185,7 +191,8 @@ class TaskContainer { // If the task properties equals regardless of starting position, don't // need to update the container. - return mInfo.getConfiguration().diffPublicOnly(configuration) != 0 + return mTaskFragmentContainersNeedsUpdate + || mInfo.getConfiguration().diffPublicOnly(configuration) != 0 || mInfo.getDisplayId() != info.getDisplayId(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt index a3111b31a2f9..169e122c353c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt @@ -61,8 +61,7 @@ abstract class CrossActivityBackAnimation( private val context: Context, private val background: BackAnimationBackground, private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, - protected val transaction: SurfaceControl.Transaction, - private val choreographer: Choreographer + protected val transaction: SurfaceControl.Transaction ) : ShellBackAnimation() { protected val startClosingRect = RectF() @@ -269,7 +268,9 @@ abstract class CrossActivityBackAnimation( .setSpring(postCommitFlingSpring) flingAnimation.start() // do an animation-frame immediately to prevent idle frame - flingAnimation.doAnimationFrame(choreographer.lastFrameTimeNanos / TimeUtils.NANOS_PER_MS) + flingAnimation.doAnimationFrame( + Choreographer.getInstance().lastFrameTimeNanos / TimeUtils.NANOS_PER_MS + ) val valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(getPostCommitAnimationDuration()) @@ -324,6 +325,7 @@ abstract class CrossActivityBackAnimation( enteringHasSameLetterbox = false lastPostCommitFlingScale = SPRING_SCALE gestureProgress = 0f + triggerBack = false } protected fun applyTransform( @@ -361,7 +363,7 @@ abstract class CrossActivityBackAnimation( } protected fun applyTransaction() { - transaction.setFrameTimelineVsync(choreographer.vsyncId) + transaction.setFrameTimelineVsync(Choreographer.getInstance().vsyncId) transaction.apply() } @@ -499,10 +501,12 @@ abstract class CrossActivityBackAnimation( } override fun onBackCancelled() { + triggerBack = false progressAnimator.onBackCancelled { finishAnimation() } } override fun onBackInvoked() { + triggerBack = true progressAnimator.reset() onGestureCommitted(progressAnimator.velocity) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt index c738ce542f8a..9ebab6383416 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt @@ -19,7 +19,6 @@ import android.content.Context import android.graphics.Rect import android.graphics.RectF import android.util.MathUtils -import android.view.Choreographer import android.view.SurfaceControl import android.view.animation.Animation import android.view.animation.Transformation @@ -31,27 +30,23 @@ import com.android.internal.policy.TransitionAnimation import com.android.internal.protolog.common.ProtoLog import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.protolog.ShellProtoLogGroup -import com.android.wm.shell.shared.annotations.ShellMainThread import javax.inject.Inject import kotlin.math.max import kotlin.math.min /** Class that handles customized predictive cross activity back animations. */ -@ShellMainThread class CustomCrossActivityBackAnimation( context: Context, background: BackAnimationBackground, rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer, transaction: SurfaceControl.Transaction, - choreographer: Choreographer, private val customAnimationLoader: CustomAnimationLoader ) : CrossActivityBackAnimation( context, background, rootTaskDisplayAreaOrganizer, - transaction, - choreographer + transaction ) { private var enterAnimation: Animation? = null @@ -70,7 +65,6 @@ class CustomCrossActivityBackAnimation( background, rootTaskDisplayAreaOrganizer, SurfaceControl.Transaction(), - Choreographer.getInstance(), CustomAnimationLoader( TransitionAnimation(context, false /* debug */, "CustomCrossActivityBackAnimation") ) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt index 3b5eb3613d2a..c747e1e98956 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt @@ -16,18 +16,15 @@ package com.android.wm.shell.back import android.content.Context -import android.view.Choreographer import android.view.SurfaceControl import android.window.BackEvent import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.animation.Interpolators -import com.android.wm.shell.shared.annotations.ShellMainThread import javax.inject.Inject import kotlin.math.max /** Class that defines cross-activity animation. */ -@ShellMainThread class DefaultCrossActivityBackAnimation @Inject constructor( @@ -39,8 +36,7 @@ constructor( context, background, rootTaskDisplayAreaOrganizer, - SurfaceControl.Transaction(), - Choreographer.getInstance() + SurfaceControl.Transaction() ) { private val postCommitInterpolator = Interpolators.EMPHASIZED diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java index 6ffeb97f50fa..58007b50350b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipBoundsAlgorithm.java @@ -27,7 +27,9 @@ import android.util.DisplayMetrics; import android.util.Size; import android.view.Gravity; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.R; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.io.PrintWriter; @@ -39,6 +41,9 @@ public class PipBoundsAlgorithm { private static final String TAG = PipBoundsAlgorithm.class.getSimpleName(); private static final float INVALID_SNAP_FRACTION = -1f; + // The same value (with the same name) is used in Launcher. + private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.01f; + @NonNull private final PipBoundsState mPipBoundsState; @NonNull protected final PipDisplayLayoutState mPipDisplayLayoutState; @NonNull protected final SizeSpecSource mSizeSpecSource; @@ -206,9 +211,27 @@ public class PipBoundsAlgorithm { */ public static boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) { - return sourceRectHint != null - && sourceRectHint.width() > destinationBounds.width() - && sourceRectHint.height() > destinationBounds.height(); + if (sourceRectHint == null || sourceRectHint.isEmpty()) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "isSourceRectHintValidForEnterPip=false, empty hint"); + return false; + } + if (sourceRectHint.width() <= destinationBounds.width() + || sourceRectHint.height() <= destinationBounds.height()) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "isSourceRectHintValidForEnterPip=false, hint(%s) is smaller" + + " than destination(%s)", sourceRectHint, destinationBounds); + return false; + } + final float reportedRatio = destinationBounds.width() / (float) destinationBounds.height(); + final float inferredRatio = sourceRectHint.width() / (float) sourceRectHint.height(); + if (Math.abs(reportedRatio - inferredRatio) > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "isSourceRectHintValidForEnterPip=false, hint(%s) does not match" + + " destination(%s) aspect ratio", sourceRectHint, destinationBounds); + return false; + } + return true; } public float getDefaultAspectRatio() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java index de016d3ae400..5097ed8866c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java @@ -269,7 +269,9 @@ public class SplitDecorManager extends WindowlessWindowManager { if (update) { if (immediately) { + t.setAlpha(mBackgroundLeash, showVeil ? 1f : 0f); t.setVisibility(mBackgroundLeash, showVeil); + t.setAlpha(mIconLeash, showVeil ? 1f : 0f); t.setVisibility(mIconLeash, showVeil); } else { startFadeAnimation(showVeil, false, null); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java index bfac24b81d2f..2520c25613e7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java @@ -258,9 +258,15 @@ public class CompatUIController implements OnDisplaysChangedListener, return; } // We're showing the first reachability education so we ignore incoming TaskInfo - // until the education flow has completed or we double tap. + // until the education flow has completed or we double tap. The double-tap + // basically cancel all the onboarding flow. We don't have to ignore events in case + // the app is in size compat mode. if (mIsFirstReachabilityEducationRunning) { - return; + if (!taskInfo.appCompatTaskInfo.isFromLetterboxDoubleTap + && !taskInfo.appCompatTaskInfo.topActivityInSizeCompat) { + return; + } + mIsFirstReachabilityEducationRunning = false; } if (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed) { if (taskInfo.appCompatTaskInfo.isLetterboxEducationEnabled) { @@ -278,17 +284,24 @@ public class CompatUIController implements OnDisplaysChangedListener, final boolean isFirstTimeVerticalReachabilityEdu = !topActivityPillarboxed && !mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(taskInfo); if (isFirstTimeHorizontalReachabilityEdu || isFirstTimeVerticalReachabilityEdu) { - mIsFirstReachabilityEducationRunning = true; mCompatUIConfiguration.setSeenLetterboxEducation(taskInfo.userId); - createOrUpdateReachabilityEduLayout(taskInfo, taskListener); - return; + // We activate the first reachability education if the double-tap is enabled. + // If the double tap is not enabled (e.g. thin letterbox) we just set the value + // of the education being seen. + if (taskInfo.appCompatTaskInfo.isLetterboxDoubleTapEnabled) { + mIsFirstReachabilityEducationRunning = true; + createOrUpdateReachabilityEduLayout(taskInfo, taskListener); + return; + } } } } createOrUpdateCompatLayout(taskInfo, taskListener); createOrUpdateRestartDialogLayout(taskInfo, taskListener); if (mCompatUIConfiguration.getHasSeenLetterboxEducation(taskInfo.userId)) { - createOrUpdateReachabilityEduLayout(taskInfo, taskListener); + if (taskInfo.appCompatTaskInfo.isLetterboxDoubleTapEnabled) { + createOrUpdateReachabilityEduLayout(taskInfo, taskListener); + } // The user aspect ratio button should not be handled when a new TaskInfo is // sent because of a double tap or when in multi-window mode. if (taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index e4420d73886e..3ba27961b713 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -63,7 +63,6 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.os.RemoteException; import android.os.SystemProperties; -import android.util.Rational; import android.view.Choreographer; import android.view.Display; import android.view.Surface; @@ -128,8 +127,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, SystemProperties.getInt( "persist.wm.debug.extra_content_overlay_fade_out_delay_ms", 400); - private static final float PIP_ASPECT_RATIO_MISMATCH_THRESHOLD = 0.005f; - private final Context mContext; private final SyncTransactionQueue mSyncTransactionQueue; private final PipBoundsState mPipBoundsState; @@ -821,37 +818,6 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPictureInPictureParams.getTitle()); mPipParamsChangedForwarder.notifySubtitleChanged( mPictureInPictureParams.getSubtitle()); - - if (mPictureInPictureParams.hasSourceBoundsHint() - && mPictureInPictureParams.hasSetAspectRatio()) { - Rational sourceRectHintAspectRatio = new Rational( - mPictureInPictureParams.getSourceRectHint().width(), - mPictureInPictureParams.getSourceRectHint().height()); - if (sourceRectHintAspectRatio.compareTo( - mPictureInPictureParams.getAspectRatio()) != 0) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "Aspect ratio of source rect hint (%d/%d) does not match the provided " - + "aspect ratio value (%d/%d). Consider matching them for " - + "improved animation. Future releases might override the " - + "value to match.", - mPictureInPictureParams.getSourceRectHint().width(), - mPictureInPictureParams.getSourceRectHint().height(), - mPictureInPictureParams.getAspectRatio().getNumerator(), - mPictureInPictureParams.getAspectRatio().getDenominator()); - } - if (Math.abs(sourceRectHintAspectRatio.floatValue() - - mPictureInPictureParams.getAspectRatioFloat()) - > PIP_ASPECT_RATIO_MISMATCH_THRESHOLD) { - ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "Aspect ratio of source rect hint (%f) does not match the provided " - + "aspect ratio value (%f) and is above threshold of %f. " - + "Consider matching them for improved animation. Future " - + "releases might override the value to match.", - sourceRectHintAspectRatio.floatValue(), - mPictureInPictureParams.getAspectRatioFloat(), - PIP_ASPECT_RATIO_MISMATCH_THRESHOLD); - } - } } mPipUiEventLoggerLogger.setTaskInfo(mTaskInfo); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java index 0541a0287179..b31ef2b6ad4e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java @@ -68,6 +68,7 @@ class SplitScreenTransitions { DismissSession mPendingDismiss = null; EnterSession mPendingEnter = null; TransitSession mPendingResize = null; + TransitSession mPendingRemotePassthrough = null; private IBinder mAnimatingTransition = null; private OneShotRemoteHandler mActiveRemoteHandler = null; @@ -320,6 +321,11 @@ class SplitScreenTransitions { return mPendingResize != null && mPendingResize.mTransition == transition; } + boolean isPendingPassThrough(IBinder transition) { + return mPendingRemotePassthrough != null && + mPendingRemotePassthrough.mTransition == transition; + } + @Nullable private TransitSession getPendingTransition(IBinder transition) { if (isPendingEnter(transition)) { @@ -331,6 +337,9 @@ class SplitScreenTransitions { } else if (isPendingResize(transition)) { ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved resize transition"); return mPendingResize; + } else if (isPendingPassThrough(transition)) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "\tresolved passThrough transition"); + return mPendingRemotePassthrough; } return null; } @@ -378,6 +387,19 @@ class SplitScreenTransitions { extraTransitType, resizeAnim); } + /** Sets a transition to enter split. */ + void setRemotePassThroughTransition(@NonNull IBinder transition, + @Nullable RemoteTransition remoteTransition) { + mPendingRemotePassthrough = new TransitSession( + transition, null, null, + remoteTransition, Transitions.TRANSIT_SPLIT_PASSTHROUGH); + + ProtoLog.v(WM_SHELL_TRANSITIONS, " splitTransition " + + " deduced remote passthrough split screen"); + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setRemotePassThrough: transitType=%d remote=%s", + Transitions.TRANSIT_SPLIT_PASSTHROUGH, remoteTransition); + } + /** Starts a transition to dismiss split. */ IBinder startDismissTransition(WindowContainerTransaction wct, Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop, @@ -474,6 +496,12 @@ class SplitScreenTransitions { mPendingResize.onConsumed(aborted); mPendingResize = null; ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for resize transition"); + } else if (isPendingPassThrough(transition)) { + mPendingRemotePassthrough.onConsumed(aborted); + mPendingRemotePassthrough.mRemoteHandler.onTransitionConsumed(transition, aborted, + finishT); + mPendingRemotePassthrough = null; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onTransitionConsumed for passThrough transition"); } // TODO: handle transition consumed for active remote handler @@ -495,6 +523,10 @@ class SplitScreenTransitions { mPendingResize.onFinished(wct, mFinishTransaction); mPendingResize = null; ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for resize transition"); + } else if (isPendingPassThrough(mAnimatingTransition)) { + mPendingRemotePassthrough.onFinished(wct, mFinishTransaction); + mPendingRemotePassthrough = null; + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "onFinish for passThrough transition"); } mActiveRemoteHandler = null; 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 b6a18e537600..4287daa03223 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 @@ -2659,6 +2659,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, && displayChange.getStartRotation() != displayChange.getEndRotation()) { mSplitLayout.setFreezeDividerWindow(true); } + if (request.getRemoteTransition() != null) { + mSplitTransitions.setRemotePassThroughTransition(transition, + request.getRemoteTransition()); + } // Still want to monitor everything while in split-screen, so return non-null. return new WindowContainerTransaction(); } else { @@ -2985,6 +2989,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, notifySplitAnimationFinished(); return true; } + } else if (mSplitTransitions.isPendingPassThrough(transition)) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, + "startAnimation: passThrough transition=%d", info.getDebugId()); + mSplitTransitions.mPendingRemotePassthrough.mRemoteHandler.startAnimation(transition, + info, startTransaction, finishTransaction, finishCallback); + notifySplitAnimationFinished(); + return true; } return startPendingAnimation(transition, info, startTransaction, finishTransaction, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index f257e207673d..d2e6a12c583c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -28,12 +28,16 @@ import static android.view.WindowManager.TRANSIT_SLEEP; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.view.WindowManager.fixScale; +import static android.window.TransitionInfo.FLAGS_IS_NON_APP_WINDOW; import static android.window.TransitionInfo.FLAG_BACK_GESTURE_ANIMATED; +import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY; import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW; import static android.window.TransitionInfo.FLAG_IS_OCCLUDED; +import static android.window.TransitionInfo.FLAG_IS_WALLPAPER; import static android.window.TransitionInfo.FLAG_NO_ANIMATION; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; +import static com.android.window.flags.Flags.ensureWallpaperInTransitions; import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary; import static com.android.wm.shell.shared.TransitionUtil.isClosingType; import static com.android.wm.shell.shared.TransitionUtil.isOpeningType; @@ -73,6 +77,7 @@ import androidx.annotation.BinderThread; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; +import com.android.window.flags.Flags; import com.android.wm.shell.RootTaskDisplayAreaOrganizer; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -184,6 +189,9 @@ public class Transitions implements RemoteCallable<Transitions>, // TRANSIT_FIRST_CUSTOM + 17 TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_DRAG_RESIZE; + /** Remote Transition that split accepts but ultimately needs to be animated by the remote. */ + public static final int TRANSIT_SPLIT_PASSTHROUGH = TRANSIT_FIRST_CUSTOM + 18; + /** Transition type for desktop mode transitions. */ public static final int TRANSIT_DESKTOP_MODE_TYPES = WindowManager.TRANSIT_FIRST_CUSTOM + 100; @@ -512,12 +520,17 @@ public class Transitions implements RemoteCallable<Transitions>, boolean isOpening = isOpeningType(info.getType()); for (int i = info.getChanges().size() - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); - if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) { + if (change.hasFlags(FLAGS_IS_NON_APP_WINDOW & ~FLAG_IS_WALLPAPER)) { // Currently system windows are controlled by WindowState, so don't change their // surfaces. Otherwise their surfaces could be hidden or cropped unexpectedly. - // This includes Wallpaper (always z-ordered at bottom) and IME (associated with - // app), because there may not be a transition associated with their visibility - // changes, and currently they don't need transition animation. + // This includes IME (associated with app), because there may not be a transition + // associated with their visibility changes, and currently they don't need a + // transition animation. + continue; + } + if (change.hasFlags(FLAG_IS_WALLPAPER) && !ensureWallpaperInTransitions()) { + // Wallpaper is always z-ordered at bottom, and historically is not animated by + // transition handlers. continue; } final SurfaceControl leash = change.getLeash(); @@ -570,6 +583,14 @@ public class Transitions implements RemoteCallable<Transitions>, final boolean isOpening = isOpeningType(transitType); final boolean isClosing = isClosingType(transitType); final int mode = change.getMode(); + // Ensure wallpapers stay in the back + if (change.hasFlags(FLAG_IS_WALLPAPER) && Flags.ensureWallpaperInTransitions()) { + if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { + return -zSplitLine + numChanges - i; + } else { + return -zSplitLine - i; + } + } // Put all the OPEN/SHOW on top if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) { if (isOpening) { @@ -788,14 +809,16 @@ public class Transitions implements RemoteCallable<Transitions>, final int changeSize = info.getChanges().size(); boolean taskChange = false; boolean transferStartingWindow = false; - int noAnimationBehindStartingWindow = 0; + int animBehindStartingWindow = 0; boolean allOccluded = changeSize > 0; for (int i = changeSize - 1; i >= 0; --i) { final TransitionInfo.Change change = info.getChanges().get(i); taskChange |= change.getTaskInfo() != null; transferStartingWindow |= change.hasFlags(FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT); - if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION)) { - noAnimationBehindStartingWindow++; + if (change.hasAllFlags(FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_NO_ANIMATION) + || change.hasAllFlags( + FLAG_IS_BEHIND_STARTING_WINDOW | FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY)) { + animBehindStartingWindow++; } if (!change.hasFlags(FLAG_IS_OCCLUDED)) { allOccluded = false; @@ -813,11 +836,11 @@ public class Transitions implements RemoteCallable<Transitions>, // There does not need animation when: // A. Transfer starting window. Apply transfer starting window directly if there is no other // task change. Since this is an activity->activity situation, we can detect it by selecting - // transitions with only 2 changes where - // 1. neither are tasks, and + // transitions with changes where + // 1. none are tasks, and // 2. one is a starting-window recipient, or all change is behind starting window. - if (!taskChange && (transferStartingWindow || noAnimationBehindStartingWindow == changeSize) - && changeSize == 2 + if (!taskChange && (transferStartingWindow || animBehindStartingWindow == changeSize) + && changeSize >= 1 // B. It's visibility change if the TRANSIT_TO_BACK/TO_FRONT happened when all // changes are underneath another change. || ((info.getType() == TRANSIT_TO_BACK || info.getType() == TRANSIT_TO_FRONT) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt index 8bf011192347..080ad901c656 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/CustomCrossActivityBackAnimationTest.kt @@ -25,7 +25,6 @@ import android.graphics.Rect import android.os.RemoteException import android.testing.AndroidTestingRunner import android.testing.TestableLooper -import android.view.Choreographer import android.view.RemoteAnimationTarget import android.view.SurfaceControl import android.view.SurfaceControl.Transaction @@ -37,8 +36,6 @@ import androidx.test.filters.SmallTest import com.android.internal.policy.TransitionAnimation import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase -import java.util.concurrent.CountDownLatch -import java.util.concurrent.TimeUnit import junit.framework.TestCase.assertEquals import org.junit.Assert import org.junit.Before @@ -50,12 +47,13 @@ import org.mockito.ArgumentMatchers.anyFloat import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock -import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.whenever +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit @SmallTest @TestableLooper.RunWithLooper @@ -82,7 +80,6 @@ class CustomCrossActivityBackAnimationTest : ShellTestCase() { backAnimationBackground, rootTaskDisplayAreaOrganizer, transaction, - mock(Choreographer::class.java), customAnimationLoader ) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index 34b2eebb15a1..ae226b807d13 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -50,8 +50,10 @@ import static org.mockito.Mockito.verify; import android.annotation.NonNull; import android.app.ActivityManager; import android.os.IBinder; +import android.os.RemoteException; import android.view.SurfaceControl; import android.view.SurfaceSession; +import android.window.IRemoteTransition; import android.window.RemoteTransition; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; @@ -326,6 +328,32 @@ public class SplitTransitionTests extends ShellTestCase { @Test @UiThreadTest + public void testRemotePassThroughInvoked() throws RemoteException { + RemoteTransition remoteWrapper = mock(RemoteTransition.class); + IRemoteTransition remoteTransition = mock(IRemoteTransition.class); + IBinder remoteBinder = mock(IBinder.class); + doReturn(remoteBinder).when(remoteTransition).asBinder(); + doReturn(remoteTransition).when(remoteWrapper).getRemoteTransition(); + + TransitionRequestInfo request = new TransitionRequestInfo(TRANSIT_CHANGE, null, + remoteWrapper); + IBinder transition = mock(IBinder.class); + mMainStage.activate(new WindowContainerTransaction(), false); + mStageCoordinator.handleRequest(transition, request); + TransitionInfo info = new TransitionInfoBuilder(TRANSIT_CHANGE, 0) + .build(); + boolean accepted = mStageCoordinator.startAnimation(transition, info, + mock(SurfaceControl.Transaction.class), + mock(SurfaceControl.Transaction.class), + mock(Transitions.TransitionFinishCallback.class)); + assertTrue(accepted); + + verify(remoteTransition, times(1)).startAnimation(any(), + any(), any(), any()); + } + + @Test + @UiThreadTest public void testEnterRecentsAndRestore() { enterSplit(); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 386a606c61c6..a4d0c1714e23 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -7005,6 +7005,23 @@ public class AudioManager { } /** + * Check whether a user can mute this stream type from a given UI element. + * + * <p>Only useful for volume controllers. + * + * @param streamType type of stream to check if it's mutable from UI + * + * @hide + */ + public boolean isStreamMutableByUi(int streamType) { + try { + return getService().isStreamMutableByUi(streamType); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Only useful for volume controllers. * @hide */ diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index c8b9da50b082..08cc126fb1a8 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -305,6 +305,8 @@ interface IAudioService { boolean isStreamAffectedByMute(int streamType); + boolean isStreamMutableByUi(int streamType); + void disableSafeMediaVolume(String callingPackage); oneway void lowerVolumeToRs1(String callingPackage); diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java index 0589c0f12ab6..e048d5c56cfe 100644 --- a/media/java/android/media/MediaRoute2Info.java +++ b/media/java/android/media/MediaRoute2Info.java @@ -26,6 +26,7 @@ import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.net.Uri; import android.os.Bundle; @@ -813,6 +814,34 @@ public final class MediaRoute2Info implements Parcelable { || mAllowedPackages.contains(packageName); } + /** + * Returns whether this route's type can only be published by the system route provider. + * + * @see #isSystemRoute() + * @hide + */ + // The default case catches all other types. + @SuppressLint("SwitchIntDef") + public boolean isSystemRouteType() { + return switch (mType) { + case TYPE_BUILTIN_SPEAKER, + TYPE_BLUETOOTH_A2DP, + TYPE_DOCK, + TYPE_BLE_HEADSET, + TYPE_HEARING_AID, + TYPE_HDMI, + TYPE_HDMI_ARC, + TYPE_HDMI_EARC, + TYPE_USB_ACCESSORY, + TYPE_USB_DEVICE, + TYPE_USB_HEADSET, + TYPE_WIRED_HEADPHONES, + TYPE_WIRED_HEADSET -> + true; + default -> false; + }; + } + /** Returns the route suitability status. */ @SuitabilityStatus @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES) diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java index cce3d4fc67d5..a14f1fd15f27 100644 --- a/media/java/android/media/MediaRoute2ProviderService.java +++ b/media/java/android/media/MediaRoute2ProviderService.java @@ -475,9 +475,25 @@ public abstract class MediaRoute2ProviderService extends Service { */ public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) { Objects.requireNonNull(routes, "routes must not be null"); - mProviderInfo = new MediaRoute2ProviderInfo.Builder() - .addRoutes(routes) - .build(); + List<MediaRoute2Info> sanitizedRoutes = new ArrayList<>(routes.size()); + + for (MediaRoute2Info route : routes) { + if (route.isSystemRouteType()) { + Log.w( + TAG, + "Attempting to add a system route type from a non-system route " + + "provider. Overriding type to TYPE_UNKNOWN. Route: " + + route); + sanitizedRoutes.add( + new MediaRoute2Info.Builder(route) + .setType(MediaRoute2Info.TYPE_UNKNOWN) + .build()); + } else { + sanitizedRoutes.add(route); + } + } + + mProviderInfo = new MediaRoute2ProviderInfo.Builder().addRoutes(sanitizedRoutes).build(); schedulePublishState(); } diff --git a/packages/PackageInstaller/res/layout/install_content_view.xml b/packages/PackageInstaller/res/layout/install_content_view.xml index 524a88a638ad..affcca1ccaed 100644 --- a/packages/PackageInstaller/res/layout/install_content_view.xml +++ b/packages/PackageInstaller/res/layout/install_content_view.xml @@ -34,7 +34,7 @@ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" + style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" android:text="@string/message_staging" /> <ProgressBar @@ -57,7 +57,7 @@ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" + style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" android:text="@string/installing" /> <ProgressBar @@ -74,7 +74,7 @@ android:id="@+id/install_confirm_question" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" + style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" android:text="@string/install_confirm_question" android:visibility="invisible" android:scrollbars="vertical" /> @@ -83,7 +83,7 @@ android:id="@+id/install_confirm_question_update" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" + style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" android:text="@string/install_confirm_question_update" android:visibility="invisible" android:scrollbars="vertical" /> @@ -92,7 +92,7 @@ android:id="@+id/install_success" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" + style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" android:text="@string/install_done" android:visibility="invisible" /> @@ -100,7 +100,7 @@ android:id="@+id/install_failed" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" + style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" android:text="@string/install_failed" android:visibility="invisible" /> @@ -108,7 +108,7 @@ android:id="@+id/install_failed_blocked" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" + style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" android:text="@string/install_failed_blocked" android:visibility="invisible" /> @@ -116,7 +116,7 @@ android:id="@+id/install_failed_conflict" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" + style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" android:text="@string/install_failed_conflict" android:visibility="invisible" /> @@ -124,7 +124,7 @@ android:id="@+id/install_failed_incompatible" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" + style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" android:text="@string/install_failed_incompatible" android:visibility="invisible" /> @@ -132,7 +132,7 @@ android:id="@+id/install_failed_invalid_apk" android:layout_width="wrap_content" android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" + style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" android:text="@string/install_failed_invalid_apk" android:visibility="invisible" /> diff --git a/packages/PackageInstaller/res/layout/uninstall_content_view.xml b/packages/PackageInstaller/res/layout/uninstall_content_view.xml index 434e33323ba1..d5c5d8b986c6 100644 --- a/packages/PackageInstaller/res/layout/uninstall_content_view.xml +++ b/packages/PackageInstaller/res/layout/uninstall_content_view.xml @@ -22,32 +22,32 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - <LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:theme="?android:attr/alertDialogTheme" - android:orientation="vertical" - android:paddingTop="8dp" - android:paddingStart="?android:attr/dialogPreferredPadding" - android:paddingEnd="?android:attr/dialogPreferredPadding" - android:clipToPadding="false"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:theme="?android:attr/alertDialogTheme" + android:orientation="vertical" + android:paddingTop="8dp" + android:paddingStart="?android:attr/dialogPreferredPadding" + android:paddingEnd="?android:attr/dialogPreferredPadding" + android:clipToPadding="false"> - <TextView - android:id="@+id/message" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@android:style/TextAppearance.Material.Subhead" /> + <TextView + android:id="@+id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" /> - <CheckBox - android:id="@+id/keepData" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="8dp" - android:layout_marginStart="-8dp" - android:paddingLeft="8sp" - android:visibility="gone" - style="@android:style/TextAppearance.Material.Subhead" /> + <CheckBox + android:id="@+id/keepData" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginStart="-8dp" + android:paddingStart="8sp" + android:paddingEnd="0sp" + android:visibility="gone" + style="@android:style/TextAppearance.DeviceDefault.SearchResult.Subtitle" /> - </LinearLayout> + </LinearLayout> </ScrollView> diff --git a/packages/PackageInstaller/res/values-night/themes.xml b/packages/PackageInstaller/res/values-night/themes.xml index a5b82b39344c..37588ad89221 100644 --- a/packages/PackageInstaller/res/values-night/themes.xml +++ b/packages/PackageInstaller/res/values-night/themes.xml @@ -19,7 +19,6 @@ <style name="Theme.AlertDialogActivity" parent="@android:style/Theme.DeviceDefault.Dialog.Alert"> - <item name="alertDialogStyle">@style/AlertDialog</item> <item name="android:windowActionBar">false</item> <item name="android:windowNoTitle">true</item> <item name="android:windowAnimationStyle">@null</item> diff --git a/packages/PackageInstaller/res/values/themes.xml b/packages/PackageInstaller/res/values/themes.xml index f5af510111cd..aa487122994e 100644 --- a/packages/PackageInstaller/res/values/themes.xml +++ b/packages/PackageInstaller/res/values/themes.xml @@ -19,7 +19,6 @@ <style name="Theme.AlertDialogActivity" parent="@android:style/Theme.DeviceDefault.Light.Dialog.Alert"> - <item name="alertDialogStyle">@style/AlertDialog</item> <item name="android:windowActionBar">false</item> <item name="android:windowNoTitle">true</item> <item name="android:windowAnimationStyle">@null</item> diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java index e0398aa49dc9..824dd4a5fdaf 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java @@ -330,7 +330,8 @@ public class PackageInstallerActivity extends Activity { // data we still want to count it as "installed". mAppInfo = mPm.getApplicationInfo(pkgName, PackageManager.MATCH_UNINSTALLED_PACKAGES); - if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { + // If the package is archived, treat it as update case. + if (!mAppInfo.isArchived && (mAppInfo.flags & ApplicationInfo.FLAG_INSTALLED) == 0) { mAppInfo = null; } } catch (NameNotFoundException e) { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt index 2e9b7b421a7b..a23df64f3582 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt +++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/model/InstallRepository.kt @@ -97,6 +97,7 @@ class InstallRepository(private val context: Context) { private set private var callingUid = Process.INVALID_UID private var originatingUid = Process.INVALID_UID + private var originatingUidFromSessionInfo = Process.INVALID_UID private var callingPackage: String? = null private var sessionStager: SessionStager? = null private lateinit var intent: Intent @@ -136,17 +137,25 @@ class InstallRepository(private val context: Context) { callingPackage = callerInfo.packageName - if (sessionId != SessionInfo.INVALID_ID) { - val sessionInfo: SessionInfo? = packageInstaller.getSessionInfo(sessionId) - callingPackage = sessionInfo?.getInstallerPackageName() - callingAttributionTag = sessionInfo?.getInstallerAttributionTag() - } - // Uid of the source package, coming from ActivityManager callingUid = callerInfo.uid if (callingUid == Process.INVALID_UID) { Log.e(LOG_TAG, "Could not determine the launching uid.") } + + originatingUidFromSessionInfo = callingUid + val sessionInfo: SessionInfo? = + if (sessionId != SessionInfo.INVALID_ID) + packageInstaller.getSessionInfo(sessionId) + else null + if (sessionInfo != null) { + callingPackage = sessionInfo.installerPackageName + callingAttributionTag = sessionInfo.installerAttributionTag + if (sessionInfo.originatingUid != Process.INVALID_UID) { + originatingUidFromSessionInfo = sessionInfo.originatingUid + } + } + val sourceInfo: ApplicationInfo? = getSourceInfo(callingPackage) // Uid of the source package, with a preference to uid from ApplicationInfo originatingUid = sourceInfo?.uid ?: callingUid @@ -651,7 +660,12 @@ class InstallRepository(private val context: Context) { private fun getUpdateMessage(pkgInfo: PackageInfo, userActionReason: Int): String? { if (isAppUpdating(pkgInfo)) { val existingUpdateOwnerLabel = getExistingUpdateOwnerLabel(pkgInfo) - val requestedUpdateOwnerLabel = getApplicationLabel(callingPackage) + + val originatingPackageNameFromSessionInfo = + getPackageNameForUid(context, originatingUidFromSessionInfo, callingPackage) + val requestedUpdateOwnerLabel = + getApplicationLabel(originatingPackageNameFromSessionInfo) + if (!TextUtils.isEmpty(existingUpdateOwnerLabel) && userActionReason == PackageInstaller.REASON_REMIND_OWNERSHIP ) { diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt index c9934adfad22..fb23637a9f4c 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt @@ -115,7 +115,7 @@ internal class RestrictedSwitchPreferenceModel( content: @Composable (SwitchPreferenceModel) -> Unit, ) { val context = LocalContext.current - val restrictedSwitchPreferenceModel = remember(restrictedMode, model.title) { + val restrictedSwitchPreferenceModel = remember(restrictedMode, model) { RestrictedSwitchPreferenceModel(context, model, restrictedMode) } restrictedSwitchPreferenceModel.RestrictionWrapper { diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 32557b979bf5..a1587562d681 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -79,3 +79,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "enable_determining_spatial_audio_attributes_by_profile" + namespace: "cross_device_experiences" + description: "Use bluetooth profile connection policy to determine spatial audio attributes" + bug: "341005211" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index b7758de0e19c..4e1d8e38dfbd 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -671,7 +671,7 @@ public abstract class InfoMediaManager { // MediaRoute2Info.getType was made public on API 34, but exists since API 30. @SuppressWarnings("NewApi") @VisibleForTesting - void addMediaDevice(MediaRoute2Info route, RoutingSessionInfo activeSession) { + void addMediaDevice(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo activeSession) { final int deviceType = route.getType(); MediaDevice mediaDevice = null; switch (deviceType) { @@ -711,8 +711,13 @@ public abstract class InfoMediaManager { case TYPE_HEARING_AID: case TYPE_BLUETOOTH_A2DP: case TYPE_BLE_HEADSET: + if (route.getAddress() == null) { + Log.e(TAG, "Ignoring bluetooth route with no set address: " + route); + break; + } final BluetoothDevice device = - BluetoothAdapter.getDefaultAdapter().getRemoteDevice(route.getAddress()); + BluetoothAdapter.getDefaultAdapter() + .getRemoteDevice(route.getAddress()); final CachedBluetoothDevice cachedDevice = mBluetoothManager.getCachedDeviceManager().findDevice(device); if (cachedDevice != null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt index 20b949f4a30f..8e072e632f3d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioRepository.kt @@ -20,6 +20,7 @@ import android.content.ContentResolver import android.database.ContentObserver import android.media.AudioDeviceInfo import android.media.AudioManager +import android.media.AudioManager.AudioDeviceCategory import android.media.AudioManager.OnCommunicationDeviceChangedListener import android.provider.Settings import androidx.concurrent.futures.DirectExecutor @@ -85,6 +86,10 @@ interface AudioRepository { suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean): Boolean suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) + + /** Gets audio device category. */ + @AudioDeviceCategory + suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int } class AudioRepositoryImpl( @@ -174,7 +179,7 @@ class AudioRepositoryImpl( minVolume = getMinVolume(audioStream), maxVolume = audioManager.getStreamMaxVolume(audioStream.value), volume = audioManager.getStreamVolume(audioStream.value), - isAffectedByMute = audioManager.isStreamAffectedByMute(audioStream.value), + isAffectedByMute = audioManager.isStreamMutableByUi(audioStream.value), isAffectedByRingerMode = audioManager.isStreamAffectedByRingerMode(audioStream.value), isMuted = audioManager.isStreamMute(audioStream.value), ) @@ -211,6 +216,13 @@ class AudioRepositoryImpl( withContext(backgroundCoroutineContext) { audioManager.ringerMode = mode.value } } + @AudioDeviceCategory + override suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int { + return withContext(backgroundCoroutineContext) { + audioManager.getBluetoothAudioDeviceCategory(bluetoothAddress) + } + } + private fun getMinVolume(stream: AudioStream): Int = try { audioManager.getStreamMinVolume(stream.value) diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt index 202ff400782f..bdd582d5130b 100644 --- a/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt +++ b/packages/SettingsLib/src/com/android/settingslib/volume/domain/interactor/AudioVolumeInteractor.kt @@ -61,6 +61,10 @@ class AudioVolumeInteractor( } suspend fun setMuted(audioStream: AudioStream, isMuted: Boolean) { + val streamModel = getAudioStream(audioStream).first() + if (!streamModel.isAffectedByMute) { + return + } if (audioStream.value == AudioManager.STREAM_RING) { val mode = if (isMuted) AudioManager.RINGER_MODE_VIBRATE else AudioManager.RINGER_MODE_NORMAL diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt index 683759db95f9..844dc12ee911 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/AudioRepositoryTest.kt @@ -247,6 +247,19 @@ class AudioRepositoryTest { } } + @Test + fun getBluetoothAudioDeviceCategory() { + testScope.runTest { + `when`(audioManager.getBluetoothAudioDeviceCategory("12:34:56:78")).thenReturn( + AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES) + + val category = underTest.getBluetoothAudioDeviceCategory("12:34:56:78") + runCurrent() + + assertThat(category).isEqualTo(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES) + } + } + private fun triggerConnectedDeviceChange(communicationDevice: AudioDeviceInfo?) { verify(audioManager) .addOnCommunicationDeviceChangedListener( diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 9f2ab69acd31..8b446b2db5d6 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -134,6 +134,7 @@ public class SecureSettings { Settings.Secure.SFPS_PERFORMANT_AUTH_ENABLED, Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_LEGACY, Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS, Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index cb7ac457d991..2af1f4d136cf 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -195,6 +195,7 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_WAKE, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_LEGACY, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS, ANY_STRING_VALIDATOR); VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, ANY_STRING_VALIDATOR); VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index d54236e60dd7..00a7826aa8e8 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -44,6 +44,7 @@ import static com.android.providers.settings.SettingsState.isSystemSettingsKey; import static com.android.providers.settings.SettingsState.makeKey; import android.Manifest; +import android.aconfigd.AconfigdFlagInfo; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -1369,10 +1370,27 @@ public class SettingsProvider extends ContentProvider { } } + Map<String, AconfigdFlagInfo> aconfigFlagInfos = + settingsState.getAconfigDefaultFlags(); + for (int i = 0; i < nameCount; i++) { String name = names.get(i); Setting setting = settingsState.getSettingLocked(name); - if (prefix == null || setting.getName().startsWith(prefix)) { + if (prefix == null || name.startsWith(prefix)) { + if (Flags.ignoreXmlForReadOnlyFlags()) { + int slashIndex = name.indexOf("/"); + boolean validSlashIndex = slashIndex != -1 + && slashIndex != 0 + && slashIndex != name.length(); + if (validSlashIndex) { + String flagName = name.substring(slashIndex + 1); + AconfigdFlagInfo flagInfo = aconfigFlagInfos.get(flagName); + if (flagInfo != null && !flagInfo.getIsReadWrite()) { + continue; + } + } + } + flagsToValues.put(setting.getName(), setting.getValue()); } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 05eb0449495f..d3d30dfcea3a 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -765,6 +765,13 @@ final class SettingsState { } } + @NonNull + public Map<String, AconfigdFlagInfo> getAconfigDefaultFlags() { + synchronized (mLock) { + return mAconfigDefaultFlags; + } + } + // The settings provider must hold its lock when calling here. public Setting getSettingLocked(String name) { if (TextUtils.isEmpty(name)) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig index d20fbf591a25..4f5955b1c6ca 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig +++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig @@ -41,3 +41,14 @@ flag { description: "If this flag is detected as true on boot, writes a logfile to track storage migration correctness." bug: "328444881" } + +flag { + name: "ignore_xml_for_read_only_flags" + namespace: "core_experiments_team_internal" + description: "When enabled, ignore any flag in the SettingsProvider XML for RO flags." + bug: "345007098" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index b37db16f5786..666d939257dc 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -280,6 +280,9 @@ <!-- to adjust volume in volume panel --> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> + <!-- to get bluetooth audio device category --> + <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED" /> + <!-- to access ResolverRankerServices --> <uses-permission android:name="android.permission.BIND_RESOLVER_RANKER_SERVICE" /> diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 1cbf67ee0d0e..bfc36b84991a 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1105,3 +1105,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "translucent_occluding_activity_fix" + namespace: "systemui" + description: "Fixes occlusion animation for transluent activities" + bug: "303010980" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt index b6e4e9b13a1c..c14ee6208176 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -22,6 +22,7 @@ import android.app.PendingIntent import android.app.TaskInfo import android.app.WindowConfiguration import android.content.ComponentName +import android.graphics.Color import android.graphics.Matrix import android.graphics.Rect import android.graphics.RectF @@ -53,6 +54,7 @@ import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.Flags.activityTransitionUseLargestWindow +import com.android.systemui.Flags.translucentOccludingActivityFix import com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary import com.android.wm.shell.shared.IShellTransitions import com.android.wm.shell.shared.ShellTransitions @@ -991,7 +993,12 @@ constructor( controller.createAnimatorState() } val windowBackgroundColor = - window.taskInfo?.let { callback.getBackgroundColor(it) } ?: window.backgroundColor + if (translucentOccludingActivityFix() && window.isTranslucent) { + Color.TRANSPARENT + } else { + window.taskInfo?.let { callback.getBackgroundColor(it) } + ?: window.backgroundColor + } // TODO(b/184121838): We should somehow get the top and bottom radius of the window // instead of recomputing isExpandingFullyAbove here. diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt index e39d7edddcfd..9e857deb280a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepository.kt @@ -21,16 +21,16 @@ import com.android.systemui.shared.settings.data.repository.SecureSettingsReposi import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext /** Provides access to state related to notification settings. */ class NotificationSettingsRepository( - scope: CoroutineScope, + private val scope: CoroutineScope, private val backgroundDispatcher: CoroutineDispatcher, private val secureSettingsRepository: SecureSettingsRepository, ) { @@ -41,16 +41,15 @@ class NotificationSettingsRepository( .distinctUntilChanged() /** The current state of the notification setting. */ - val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> = + suspend fun isShowNotificationsOnLockScreenEnabled(): StateFlow<Boolean> = secureSettingsRepository .intSetting( name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, ) .map { it == 1 } + .flowOn(backgroundDispatcher) .stateIn( scope = scope, - started = SharingStarted.WhileSubscribed(), - initialValue = false, ) suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) { diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt index 04e8090e3ae2..b4105bdecc0a 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/notifications/domain/interactor/NotificationSettingsInteractor.kt @@ -26,8 +26,8 @@ class NotificationSettingsInteractor( val isNotificationHistoryEnabled = repository.isNotificationHistoryEnabled /** Should notifications be visible on the lockscreen? */ - val isShowNotificationsOnLockScreenEnabled: StateFlow<Boolean> = - repository.isShowNotificationsOnLockScreenEnabled + suspend fun isShowNotificationsOnLockScreenEnabled(): StateFlow<Boolean> = + repository.isShowNotificationsOnLockScreenEnabled() suspend fun setShowNotificationsOnLockscreenEnabled(enabled: Boolean) { repository.setShowNotificationsOnLockscreenEnabled(enabled) @@ -35,7 +35,7 @@ class NotificationSettingsInteractor( /** Toggles the setting to show or hide notifications on the lock screen. */ suspend fun toggleShowNotificationsOnLockscreenEnabled() { - val current = repository.isShowNotificationsOnLockScreenEnabled.value + val current = repository.isShowNotificationsOnLockScreenEnabled().value repository.setShowNotificationsOnLockscreenEnabled(!current) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index fa79ea01cf51..54e0725957e6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -1284,6 +1284,41 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void onAodDownAndDownTouchReceived() throws RemoteException { + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultDown = + new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, + -1 /* pointerId */, touchData); + + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // WHEN fingerprint is requested because of AOD interrupt + // GIVEN there's been an AoD interrupt + when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); + mScreenObserver.onScreenTurnedOn(); + mUdfpsController.onAodInterrupt(0, 0, 0, 0); + mFgExecutor.runAllReady(); + + // and an ACTION_DOWN is received and touch is within sensor + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDown); + MotionEvent firstDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, firstDownEvent); + mBiometricExecutor.runAllReady(); + firstDownEvent.recycle(); + + // THEN the touch is only processed once + verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), + anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), + anyBoolean()); + } + + @Test public void onTouch_pilferPointerWhenAltBouncerShowing() throws RemoteException { final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt index 73ef77540398..88ba0411b414 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayAnimationsControllerTest.kt @@ -84,7 +84,7 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { verify(mockAnimator, atLeastOnce()).addListener(captor.capture()) captor.allValues.forEach { it.onAnimationEnd(mockAnimator) } - verify(stateController).setExitAnimationsRunning(false) + verify(stateController, times(2)).setExitAnimationsRunning(false) } @Test @@ -154,4 +154,10 @@ class DreamOverlayAnimationsControllerTest : SysuiTestCase() { } ) } + + @Test + fun testCancelAnimations_clearsExitAnimationsRunning() { + controller.cancelAnimations() + verify(stateController).setExitAnimationsRunning(false) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt index 4587ea6dbdc8..c5ba02d0773a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepositoryTest.kt @@ -528,6 +528,30 @@ class BiometricSettingsRepositoryTest : SysuiTestCase() { } @Test + fun userChange_isFingerprintEnrolledAndEnabledUpdated() = + testScope.runTest { + createBiometricSettingsRepository() + whenever(authController.isFingerprintEnrolled(ANOTHER_USER_ID)).thenReturn(false) + whenever(authController.isFingerprintEnrolled(PRIMARY_USER_ID)).thenReturn(true) + + verify(biometricManager) + .registerEnabledOnKeyguardCallback(biometricManagerCallback.capture()) + val isFingerprintEnrolledAndEnabled = + collectLastValue(underTest.isFingerprintEnrolledAndEnabled) + biometricManagerCallback.value.onChanged(true, ANOTHER_USER_ID) + runCurrent() + userRepository.setSelectedUserInfo(ANOTHER_USER) + runCurrent() + assertThat(isFingerprintEnrolledAndEnabled()).isFalse() + + biometricManagerCallback.value.onChanged(true, PRIMARY_USER_ID) + runCurrent() + userRepository.setSelectedUserInfo(PRIMARY_USER) + runCurrent() + assertThat(isFingerprintEnrolledAndEnabled()).isTrue() + } + + @Test fun userChange_biometricEnabledChange_handlesRaceCondition() = testScope.runTest { createBiometricSettingsRepository() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt index d20fec44d60f..5115f5af94f0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractorTest.kt @@ -90,7 +90,11 @@ class FromAlternateBouncerTransitionInteractorTest : SysuiTestCase() { ) reset(transitionRepository) + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(null) kosmos.fakeKeyguardRepository.setKeyguardOccluded(true) + runCurrent() + assertThat(transitionRepository).noTransitionsStarted() + kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(true) runCurrent() kosmos.fakeKeyguardBouncerRepository.setKeyguardAuthenticatedBiometrics(null) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt index 6d9c271d0f4a..b49e546c6e78 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelTest.kt @@ -227,6 +227,15 @@ class DeviceEntryIconViewModelTest : SysuiTestCase() { assertThat(accessibilityDelegateHint) .isEqualTo(DeviceEntryIconView.AccessibilityHintType.BOUNCER) + // udfps running + setUpState( + isUdfpsSupported = true, + isUdfpsRunning = true, + ) + + assertThat(accessibilityDelegateHint) + .isEqualTo(DeviceEntryIconView.AccessibilityHintType.BOUNCER) + // non-interactive lock icon fingerprintPropertyRepository.supportsRearFps() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt index d620639b2219..ef68e8a513dd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioVolumeInteractorTest.kt @@ -36,6 +36,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest +import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -49,6 +50,20 @@ class AudioVolumeInteractorTest : SysuiTestCase() { private val underTest: AudioVolumeInteractor = with(kosmos) { AudioVolumeInteractor(audioRepository, notificationsSoundPolicyInteractor) } + @Before + fun setUp() = + with(kosmos) { + audioRepository.setAudioStreamModel( + audioRepository.getAudioStream(audioStream).value.copy(isAffectedByMute = true) + ) + audioRepository.setAudioStreamModel( + audioRepository + .getAudioStream(AudioStream(AudioManager.STREAM_RING)) + .value + .copy(isAffectedByMute = true) + ) + } + @Test fun setMuted_mutesStream() { with(kosmos) { @@ -194,10 +209,14 @@ class AudioVolumeInteractorTest : SysuiTestCase() { testScope.runTest { val audioStreamModel by collectLastValue(underTest.getAudioStream(audioStream)) audioRepository.setAudioStreamModel( - audioStreamModel!!.copy(isAffectedByMute = false) + audioStreamModel!!.copy(isAffectedByMute = false, isMuted = false) ) + underTest.setMuted(audioStream, true) + runCurrent() + assertThat(audioStreamModel!!.isAffectedByMute).isFalse() + assertThat(audioStreamModel!!.isMuted).isFalse() } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt index 777240c57c2e..5826b3feae37 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/SpatialAudioComponentKosmos.kt @@ -17,8 +17,10 @@ package com.android.systemui.volume.panel.component.spatial import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.kosmos.testScope import com.android.systemui.media.spatializerInteractor +import com.android.systemui.volume.data.repository.audioRepository import com.android.systemui.volume.domain.interactor.audioOutputInteractor import com.android.systemui.volume.panel.component.spatial.domain.interactor.SpatialAudioComponentInteractor @@ -27,6 +29,8 @@ val Kosmos.spatialAudioComponentInteractor by SpatialAudioComponentInteractor( audioOutputInteractor, spatializerInteractor, + audioRepository, + backgroundCoroutineContext, testScope.backgroundScope ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt index 2f69942aa459..ebc78d864494 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/SpatialAudioAvailabilityCriteriaTest.kt @@ -16,6 +16,8 @@ package com.android.systemui.volume.panel.component.spatial.domain +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothProfile import android.media.AudioDeviceAttributes import android.media.AudioDeviceInfo import android.media.session.MediaSession @@ -24,12 +26,14 @@ import android.testing.TestableLooper.RunWithLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LeAudioProfile import com.android.settingslib.media.BluetoothMediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope import com.android.systemui.media.spatializerRepository import com.android.systemui.testKosmos +import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.android.systemui.volume.localMediaController @@ -56,8 +60,15 @@ class SpatialAudioAvailabilityCriteriaTest : SysuiTestCase() { @Before fun setup() { with(kosmos) { + val leAudioProfile = + mock<LeAudioProfile> { + whenever(profileId).thenReturn(BluetoothProfile.LE_AUDIO) + whenever(isEnabled(any())).thenReturn(true) + } val cachedBluetoothDevice: CachedBluetoothDevice = mock { whenever(address).thenReturn("test_address") + whenever(profiles).thenReturn(listOf(leAudioProfile)) + whenever(device).thenReturn(mock<BluetoothDevice> {}) } localMediaRepository.updateCurrentConnectedDevice( mock<BluetoothMediaDevice> { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt index c6c46faf97f7..d5566adb1fdc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorTest.kt @@ -16,14 +16,22 @@ package com.android.systemui.volume.panel.component.spatial.domain.interactor +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothProfile import android.media.AudioDeviceAttributes import android.media.AudioDeviceInfo import android.media.session.MediaSession import android.media.session.PlaybackState +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest +import com.android.settingslib.bluetooth.A2dpProfile import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.HearingAidProfile +import com.android.settingslib.bluetooth.LeAudioProfile +import com.android.settingslib.flags.Flags import com.android.settingslib.media.BluetoothMediaDevice import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue @@ -44,6 +52,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -52,15 +61,33 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class SpatialAudioComponentInteractorTest : SysuiTestCase() { + @get:Rule val setFlagsRule = SetFlagsRule() private val kosmos = testKosmos() private lateinit var underTest: SpatialAudioComponentInteractor + private val bluetoothDevice: BluetoothDevice = mock {} + private val a2dpProfile: A2dpProfile = mock { + whenever(profileId).thenReturn(BluetoothProfile.A2DP) + whenever(isEnabled(bluetoothDevice)).thenReturn(false) + } + private val leAudioProfile: LeAudioProfile = mock { + whenever(profileId).thenReturn(BluetoothProfile.LE_AUDIO) + whenever(isEnabled(bluetoothDevice)).thenReturn(true) + } + private val hearingAidProfile: HearingAidProfile = mock { + whenever(profileId).thenReturn(BluetoothProfile.HEARING_AID) + whenever(isEnabled(bluetoothDevice)).thenReturn(false) + } + @Before fun setup() { with(kosmos) { val cachedBluetoothDevice: CachedBluetoothDevice = mock { whenever(address).thenReturn("test_address") + whenever(device).thenReturn(bluetoothDevice) + whenever(profiles) + .thenReturn(listOf(a2dpProfile, leAudioProfile, hearingAidProfile)) } localMediaRepository.updateCurrentConnectedDevice( mock<BluetoothMediaDevice> { @@ -83,7 +110,7 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { fun setEnabled_changesIsEnabled() { with(kosmos) { testScope.runTest { - spatializerRepository.setIsSpatialAudioAvailable(headset, true) + spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, true) val values by collectValues(underTest.isEnabled) underTest.setEnabled(SpatialAudioEnabledModel.Disabled) @@ -106,10 +133,39 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(Flags.FLAG_ENABLE_DETERMINING_SPATIAL_AUDIO_ATTRIBUTES_BY_PROFILE) + fun setEnabled_determinedByBluetoothProfile_a2dpProfileEnabled() { + with(kosmos) { + testScope.runTest { + whenever(a2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true) + whenever(leAudioProfile.isEnabled(bluetoothDevice)).thenReturn(false) + whenever(hearingAidProfile.isEnabled(bluetoothDevice)).thenReturn(false) + spatializerRepository.setIsSpatialAudioAvailable(a2dpAttributes, true) + val values by collectValues(underTest.isEnabled) + + underTest.setEnabled(SpatialAudioEnabledModel.Disabled) + runCurrent() + underTest.setEnabled(SpatialAudioEnabledModel.SpatialAudioEnabled) + runCurrent() + + assertThat(values) + .containsExactly( + SpatialAudioEnabledModel.Unknown, + SpatialAudioEnabledModel.Disabled, + SpatialAudioEnabledModel.SpatialAudioEnabled, + ) + .inOrder() + assertThat(spatializerRepository.getSpatialAudioCompatibleDevices()) + .containsExactly(a2dpAttributes) + } + } + } + + @Test fun connectedDeviceSupports_isAvailable_SpatialAudio() { with(kosmos) { testScope.runTest { - spatializerRepository.setIsSpatialAudioAvailable(headset, true) + spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, true) val isAvailable by collectLastValue(underTest.isAvailable) @@ -123,8 +179,8 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { fun connectedDeviceSupportsHeadTracking_isAvailable_HeadTracking() { with(kosmos) { testScope.runTest { - spatializerRepository.setIsSpatialAudioAvailable(headset, true) - spatializerRepository.setIsHeadTrackingAvailable(headset, true) + spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, true) + spatializerRepository.setIsHeadTrackingAvailable(bleHeadsetAttributes, true) val isAvailable by collectLastValue(underTest.isAvailable) @@ -138,7 +194,7 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { fun connectedDeviceDoesntSupport_isAvailable_Unavailable() { with(kosmos) { testScope.runTest { - spatializerRepository.setIsSpatialAudioAvailable(headset, false) + spatializerRepository.setIsSpatialAudioAvailable(bleHeadsetAttributes, false) val isAvailable by collectLastValue(underTest.isAvailable) @@ -179,7 +235,13 @@ class SpatialAudioComponentInteractorTest : SysuiTestCase() { } private companion object { - val headset = + val a2dpAttributes = + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + "test_address" + ) + val bleHeadsetAttributes = AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BLE_HEADSET, diff --git a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml index cf9ca157b943..c9850f2613b1 100644 --- a/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml +++ b/packages/SystemUI/res-keyguard/layout/alternate_bouncer.xml @@ -19,8 +19,6 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:sysui="http://schemas.android.com/apk/res-auto" android:id="@+id/alternate_bouncer" - android:focusable="true" - android:clickable="true" android:layout_width="match_parent" android:layout_height="match_parent"> diff --git a/packages/SystemUI/res/color/connected_network_primary_color.xml b/packages/SystemUI/res/color/connected_network_primary_color.xml new file mode 100644 index 000000000000..f173c8dd5473 --- /dev/null +++ b/packages/SystemUI/res/color/connected_network_primary_color.xml @@ -0,0 +1,20 @@ +<!-- + ~ Copyright (C) 2024 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> + <item android:color="?androidprv:attr/materialColorOnPrimaryContainer" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml index 250188b892f4..fab2d8db859f 100644 --- a/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml +++ b/packages/SystemUI/res/drawable/settingslib_switch_bar_bg_on.xml @@ -16,10 +16,11 @@ --> <ripple xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:color="?android:attr/colorControlHighlight"> <item> <shape android:shape="rectangle"> - <solid android:color="@color/settingslib_state_on_color"/> + <solid android:color="?androidprv:attr/materialColorPrimaryContainer"/> <corners android:radius="@dimen/settingslib_switch_bar_radius"/> </shape> </item> diff --git a/packages/SystemUI/res/drawable/settingslib_thumb_on.xml b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml index 5566ea3f62fc..e316a93c1c3d 100644 --- a/packages/SystemUI/res/drawable/settingslib_thumb_on.xml +++ b/packages/SystemUI/res/drawable/settingslib_thumb_on.xml @@ -15,7 +15,8 @@ limitations under the License. --> -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <item android:top="@dimen/settingslib_switch_thumb_margin" android:bottom="@dimen/settingslib_switch_thumb_margin"> @@ -23,7 +24,7 @@ <size android:height="@dimen/settingslib_switch_thumb_size" android:width="@dimen/settingslib_switch_thumb_size"/> - <solid android:color="@color/settingslib_state_on_color"/> + <solid android:color="?androidprv:attr/materialColorOnPrimary"/> </shape> </item> </layer-list> diff --git a/packages/SystemUI/res/drawable/settingslib_track_on_background.xml b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml index 1d9dacd6c0f9..e2e64684f9c3 100644 --- a/packages/SystemUI/res/drawable/settingslib_track_on_background.xml +++ b/packages/SystemUI/res/drawable/settingslib_track_on_background.xml @@ -16,11 +16,12 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:shape="rectangle" android:width="@dimen/settingslib_switch_track_width" android:height="@dimen/settingslib_switch_track_height"> <padding android:left="@dimen/settingslib_switch_thumb_margin" android:right="@dimen/settingslib_switch_thumb_margin"/> - <solid android:color="@color/settingslib_track_on_color"/> + <solid android:color="?androidprv:attr/materialColorPrimary"/> <corners android:radius="@dimen/settingslib_switch_track_radius"/> </shape> diff --git a/packages/SystemUI/res/layout/screenshot_shelf.xml b/packages/SystemUI/res/layout/screenshot_shelf.xml index 84ab0f1b6ee5..fff1de7c1049 100644 --- a/packages/SystemUI/res/layout/screenshot_shelf.xml +++ b/packages/SystemUI/res/layout/screenshot_shelf.xml @@ -51,7 +51,6 @@ android:layout_marginBottom="@dimen/overlay_border_width" android:layout_gravity="center" android:elevation="4dp" - android:contentDescription="@string/screenshot_edit_description" android:scaleType="fitEnd" android:background="@drawable/overlay_preview_background" android:adjustViewBounds="true" @@ -67,7 +66,6 @@ android:layout_marginBottom="@dimen/overlay_border_width" android:layout_gravity="center" android:elevation="4dp" - android:contentDescription="@string/screenshot_edit_description" android:scaleType="fitEnd" android:background="@drawable/overlay_preview_background" android:adjustViewBounds="true" diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml index fc4bf8a65643..e80ed26cffe5 100644 --- a/packages/SystemUI/res/layout/sidefps_view.xml +++ b/packages/SystemUI/res/layout/sidefps_view.xml @@ -22,5 +22,4 @@ android:layout_height="wrap_content" app:lottie_autoPlay="true" app:lottie_loop="true" - app:lottie_rawRes="@raw/sfps_pulse" - android:importantForAccessibility="no"/>
\ No newline at end of file + app:lottie_rawRes="@raw/sfps_pulse"/>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml index 78fec55653b7..b1cc08661c73 100644 --- a/packages/SystemUI/res/values-en-rCA/strings.xml +++ b/packages/SystemUI/res/values-en-rCA/strings.xml @@ -181,6 +181,7 @@ <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"Unlocked by face. Press to continue."</string> <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"Face recognized. Press to continue."</string> <string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"Face recognized. Press the unlock icon to continue."</string> + <string name="biometric_dialog_tap_confirm_with_face_sfps" msgid="2499213248903257928">"Unlocked by face. Tap to continue."</string> <string name="biometric_dialog_authenticated" msgid="7337147327545272484">"Authenticated"</string> <string name="biometric_dialog_cancel_authentication" msgid="981316588773442637">"Cancel Authentication"</string> <string name="biometric_dialog_content_view_more_options_button" msgid="2663810393874865475">"More Options"</string> diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml index 9a358dc016a2..8ef3602c21ec 100644 --- a/packages/SystemUI/res/values-en-rXC/strings.xml +++ b/packages/SystemUI/res/values-en-rXC/strings.xml @@ -181,6 +181,7 @@ <string name="biometric_dialog_tap_confirm_with_face_alt_1" msgid="439152621640507113">"Unlocked by face. Press to continue."</string> <string name="biometric_dialog_tap_confirm_with_face_alt_2" msgid="8586608186457385108">"Face recognized. Press to continue."</string> <string name="biometric_dialog_tap_confirm_with_face_alt_3" msgid="2192670471930606539">"Face recognized. Press the unlock icon to continue."</string> + <string name="biometric_dialog_tap_confirm_with_face_sfps" msgid="2499213248903257928">"Unlocked by face. Tap to continue."</string> <string name="biometric_dialog_authenticated" msgid="7337147327545272484">"Authenticated"</string> <string name="biometric_dialog_cancel_authentication" msgid="981316588773442637">"Cancel Authentication"</string> <string name="biometric_dialog_content_view_more_options_button" msgid="2663810393874865475">"More Options"</string> diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index d377e0196e98..21f1cfbe4c9e 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -104,10 +104,6 @@ <color name="people_tile_background">@color/material_dynamic_secondary20</color> - <!-- Internet Dialog --> - <color name="connected_network_primary_color">@color/material_dynamic_primary80</color> - <color name="connected_network_secondary_color">@color/material_dynamic_secondary80</color> - <!-- Keyboard shortcut helper dialog --> <color name="ksh_key_item_color">@*android:color/system_on_surface_variant_dark</color> </resources> diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml index 546bf1c3ac95..e9dd039f38c2 100644 --- a/packages/SystemUI/res/values-night/styles.xml +++ b/packages/SystemUI/res/values-night/styles.xml @@ -60,18 +60,6 @@ <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> </style> - <style name="TextAppearance.InternetDialog.Active"> - <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> - <item name="android:textSize">16sp</item> - <item name="android:textColor">@color/material_dynamic_primary80</item> - <item name="android:textDirection">locale</item> - </style> - - <style name="TextAppearance.InternetDialog.Secondary.Active"> - <item name="android:textSize">14sp</item> - <item name="android:textColor">@color/material_dynamic_secondary80</item> - </style> - <style name="ShortcutHelperTheme" parent="@style/ShortcutHelperThemeCommon"> <item name="android:windowLightNavigationBar">false</item> </style> diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index ba59c2f99c52..5c62cce94c11 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -237,11 +237,8 @@ <!-- Internet Dialog --> <!-- Material next state on color--> <color name="settingslib_state_on_color">@color/settingslib_state_on</color> - <!-- Material next track on color--> - <color name="settingslib_track_on_color">@color/settingslib_track_on</color> <!-- Material next track off color--> <color name="settingslib_track_off_color">@color/settingslib_track_off</color> - <color name="connected_network_primary_color">#191C18</color> <color name="connected_network_secondary_color">#41493D</color> <color name="dream_overlay_camera_mic_off_dot_color">#FCBE03</color> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 838181252cb9..d51831b5730f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -441,6 +441,8 @@ <string name="biometric_dialog_tap_confirm_with_face_alt_2">Face recognized. Press to continue.</string> <!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]--> <string name="biometric_dialog_tap_confirm_with_face_alt_3">Face recognized. Press the unlock icon to continue.</string> + <!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication with SFPS [CHAR LIMIT=60]--> + <string name="biometric_dialog_tap_confirm_with_face_sfps">Unlocked by face. Tap to continue.</string> <!-- Talkback string when a biometric is authenticated [CHAR LIMIT=NONE] --> <string name="biometric_dialog_authenticated">Authenticated</string> <!-- Talkback string when a canceling authentication [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 73b7586f1210..7475eb2eceaa 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -1315,7 +1315,7 @@ <item name="android:background">?android:attr/selectableItemBackground</item> </style> - <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault"> + <style name="MainSwitch.Settingslib" parent="@android:style/Theme.DeviceDefault.DayNight"> <item name="android:switchMinWidth">@dimen/settingslib_min_switch_width</item> </style> @@ -1358,6 +1358,7 @@ <style name="InternetDialog.NetworkTitle.Active"> <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Active</item> + <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item> </style> <style name="InternetDialog.NetworkSummary"> @@ -1370,18 +1371,19 @@ <style name="InternetDialog.NetworkSummary.Active"> <item name="android:textAppearance">@style/TextAppearance.InternetDialog.Secondary.Active </item> + <item name="android:textColor">?androidprv:attr/materialColorOnPrimaryContainer</item> </style> <style name="TextAppearance.InternetDialog"> <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> <item name="android:textSize">16sp</item> - <item name="android:textColor">?android:attr/textColorPrimary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurface</item> <item name="android:textDirection">locale</item> </style> <style name="TextAppearance.InternetDialog.Secondary"> <item name="android:textSize">14sp</item> - <item name="android:textColor">?android:attr/textColorSecondary</item> + <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item> </style> <style name="TextAppearance.InternetDialog.Active"/> diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt index 5e76801aaca3..bf1f93f4693a 100644 --- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt +++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt @@ -28,6 +28,7 @@ import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_LEGACY import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS @@ -86,6 +87,12 @@ class ActiveUnlockConfig @Inject constructor( * Trigger ActiveUnlock when the assistant is triggered. */ ASSISTANT, + /** + * Trigger ActiveUnlock on legacy unlock intents. This includes tapping on the empty space + * of the notification shadse when face auth is enrolled and re-trying face auth on the + * primary bouncer. + */ + UNLOCK_INTENT_LEGACY, } /** @@ -99,6 +106,7 @@ class ActiveUnlockConfig @Inject constructor( } private var requestActiveUnlockOnWakeup = false + private var requestActiveUnlockOnUnlockIntentLegacy = false private var requestActiveUnlockOnUnlockIntent = false private var requestActiveUnlockOnBioFail = false @@ -110,6 +118,8 @@ class ActiveUnlockConfig @Inject constructor( private val settingsObserver = object : ContentObserver(handler) { private val wakeUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE) + private val unlockIntentLegacyUri = + secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_LEGACY) private val unlockIntentUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT) private val bioFailUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL) private val faceErrorsUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS) @@ -164,6 +174,15 @@ class ActiveUnlockConfig @Inject constructor( ACTIVE_UNLOCK_ON_WAKE, 0, selectedUserInteractor.getSelectedUserId()) == 1 } + if (selfChange || uris.contains(unlockIntentLegacyUri)) { + requestActiveUnlockOnUnlockIntentLegacy = + secureSettings.getIntForUser( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_LEGACY, + 0, + selectedUserInteractor.getSelectedUserId() + ) == 1 + } + if (selfChange || uris.contains(unlockIntentUri)) { requestActiveUnlockOnUnlockIntent = secureSettings.getIntForUser( ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0, @@ -257,7 +276,7 @@ class ActiveUnlockConfig @Inject constructor( */ fun isActiveUnlockEnabled(): Boolean { return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent || - requestActiveUnlockOnBioFail + requestActiveUnlockOnBioFail || requestActiveUnlockOnUnlockIntentLegacy } /** @@ -299,15 +318,18 @@ class ActiveUnlockConfig @Inject constructor( fun shouldAllowActiveUnlockFromOrigin(requestOrigin: ActiveUnlockRequestOrigin): Boolean { return when (requestOrigin) { ActiveUnlockRequestOrigin.WAKE -> requestActiveUnlockOnWakeup - + ActiveUnlockRequestOrigin.UNLOCK_INTENT_LEGACY -> + requestActiveUnlockOnUnlockIntentLegacy ActiveUnlockRequestOrigin.UNLOCK_INTENT -> - requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup || - (shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()) - + requestActiveUnlockOnUnlockIntent || + requestActiveUnlockOnUnlockIntentLegacy || + requestActiveUnlockOnWakeup || + (shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()) ActiveUnlockRequestOrigin.BIOMETRIC_FAIL -> - requestActiveUnlockOnBioFail || requestActiveUnlockOnUnlockIntent || - requestActiveUnlockOnWakeup - + requestActiveUnlockOnBioFail || + requestActiveUnlockOnUnlockIntentLegacy || + requestActiveUnlockOnUnlockIntent || + requestActiveUnlockOnWakeup ActiveUnlockRequestOrigin.ASSISTANT -> isActiveUnlockEnabled() } } @@ -345,6 +367,9 @@ class ActiveUnlockConfig @Inject constructor( override fun dump(pw: PrintWriter, args: Array<out String>) { pw.println("Settings:") pw.println(" requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup") + pw.println( + " requestActiveUnlockOnUnlockIntentLegacy=$requestActiveUnlockOnUnlockIntentLegacy" + ) pw.println(" requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent") pw.println(" requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail") diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java index 70465bc3d0b7..421782039043 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java @@ -1097,7 +1097,8 @@ public class KeyguardSecurityContainer extends ConstraintLayout { int yTranslation = mResources.getDimensionPixelSize(R.dimen.disappear_y_translation); AnimatorSet anims = new AnimatorSet(); - ObjectAnimator yAnim = ObjectAnimator.ofFloat(mView, View.TRANSLATION_Y, yTranslation); + ObjectAnimator yAnim = ObjectAnimator.ofFloat(mViewFlipper, View.TRANSLATION_Y, + yTranslation); ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(mUserSwitcherViewGroup, View.ALPHA, 0f); diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index 42838aeddd6b..428cd0e7da0a 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -351,7 +351,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard mDeviceEntryFaceAuthInteractor.onSwipeUpOnBouncer(); if (mDeviceEntryFaceAuthInteractor.isFaceAuthEnabledAndEnrolled()) { mUpdateMonitor.requestActiveUnlock( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT, + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT_LEGACY, "swipeUpOnBouncer"); } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index d6d40f28d288..b466f31cc509 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -995,6 +995,16 @@ public class AuthController implements } /** + * @return true if ultrasonic udfps HW is supported on this device. Can return true even if + * the user has not enrolled udfps. This may be false if called before + * onAllAuthenticatorsRegistered. + */ + public boolean isUltrasonicUdfpsSupported() { + return getUdfpsProps() != null && !getUdfpsProps().isEmpty() && getUdfpsProps() + .get(0).isUltrasonicUdfps(); + } + + /** * @return true if sfps HW is supported on this device. Can return true even if the user has * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered. */ diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index ad142a86fa03..3dd375846499 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -270,6 +270,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @Override public void showUdfpsOverlay(long requestId, int sensorId, int reason, @NonNull IUdfpsOverlayControllerCallback callback) { + mUdfpsOverlayInteractor.setRequestId(requestId); mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay( new UdfpsControllerOverlay( mContext, @@ -404,6 +405,15 @@ public class UdfpsController implements DozeReceiver, Dumpable { handler::post, authenticationCallback); } + + /** + * Debug to run setIgnoreDisplayTouches + */ + public void debugSetIgnoreDisplayTouches(boolean ignoreTouch) { + final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L; + UdfpsController.this.mFingerprintManager.setIgnoreDisplayTouches( + requestId, mSensorProps.sensorId, ignoreTouch); + } } /** @@ -558,7 +568,12 @@ public class UdfpsController implements DozeReceiver, Dumpable { Log.w(TAG, "onTouch down received without a preceding up"); } mActivePointerId = MotionEvent.INVALID_POINTER_ID; - mOnFingerDown = false; + + // It's possible on some devices to get duplicate touches from both doze and the + // normal touch listener. Don't reset the down in this case to avoid duplicate downs + if (!mIsAodInterruptActive) { + mOnFingerDown = false; + } } else if (!DeviceEntryUdfpsRefactor.isEnabled()) { if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f && !mAlternateBouncerInteractor.isVisibleState()) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt index f5e3d29cb878..97ece11b8fbb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt @@ -75,6 +75,8 @@ class UdfpsShell @Inject constructor(commandRegistry: CommandRegistry) : Command simFingerUp() } else if (args.size == 1 && args[0] == "biometricPrompt") { launchBiometricPrompt() + } else if (args.size == 2 && args[0] == "setIgnoreDisplayTouches") { + setIgnoreDisplayTouches(args[1].toBoolean()) } else { invalidCommand(pw) } @@ -186,6 +188,11 @@ class UdfpsShell @Inject constructor(commandRegistry: CommandRegistry) : Command upEvent?.recycle() } + @VisibleForTesting + fun setIgnoreDisplayTouches(ignoreTouches: Boolean) { + udfpsOverlayController?.debugSetIgnoreDisplayTouches(ignoreTouches) + } + private fun obtainMotionEvent( action: Int, x: Float, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt index a77cc1fea6a6..bb450c0b6d90 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics.domain.interactor import android.content.Context +import android.hardware.fingerprint.FingerprintManager import android.util.Log import android.view.MotionEvent import com.android.systemui.biometrics.AuthController @@ -46,6 +47,7 @@ constructor( @Application private val context: Context, private val authController: AuthController, private val selectedUserInteractor: SelectedUserInteractor, + private val fingerprintManager: FingerprintManager?, @Application scope: CoroutineScope ) { private fun calculateIconSize(): Int { @@ -70,8 +72,25 @@ constructor( return isUdfpsEnrolled && isWithinOverlayBounds } + private var _requestId = MutableStateFlow(0L) + + /** RequestId of current AcquisitionClient */ + val requestId: StateFlow<Long> = _requestId.asStateFlow() + + fun setRequestId(requestId: Long) { + _requestId.value = requestId + } + /** Sets whether Udfps overlay should handle touches */ fun setHandleTouches(shouldHandle: Boolean = true) { + if (authController.isUltrasonicUdfpsSupported + && shouldHandle != _shouldHandleTouches.value) { + fingerprintManager?.setIgnoreDisplayTouches( + requestId.value, + authController.udfpsProps!!.get(0).sensorId, + !shouldHandle + ) + } _shouldHandleTouches.value = shouldHandle } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt index 408e2c2d65a1..c868d01de743 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt @@ -580,17 +580,22 @@ class Spaghetti( } } - private suspend fun getHelpForSuccessfulAuthentication( + private fun getHelpForSuccessfulAuthentication( authenticatedModality: BiometricModality, - ): Int? = - when { - // for coex, show a message when face succeeds after fingerprint has also started - modalities.hasFaceAndFingerprint && - (viewModel.fingerprintStartMode.first() != FingerprintStartMode.Pending) && - (authenticatedModality == BiometricModality.Face) -> - R.string.biometric_dialog_tap_confirm_with_face_alt_1 - else -> null + ): Int? { + // for coex, show a message when face succeeds after fingerprint has also started + if (authenticatedModality != BiometricModality.Face) { + return null + } + + if (modalities.hasUdfps) { + return R.string.biometric_dialog_tap_confirm_with_face_alt_1 } + if (modalities.hasSfps) { + return R.string.biometric_dialog_tap_confirm_with_face_sfps + } + return null + } fun onAuthenticationFailed( @BiometricAuthenticator.Modality modality: Int, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt index 9cc46506cefb..9578da4238ee 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt @@ -139,6 +139,11 @@ constructor( overlayView!!.visibility = View.INVISIBLE Log.d(TAG, "show(): adding overlayView $overlayView") windowManager.get().addView(overlayView, overlayViewModel.defaultOverlayViewParams) + overlayView!!.announceForAccessibility( + applicationContext.resources.getString( + R.string.accessibility_side_fingerprint_indicator_label + ) + ) } /** Hide the side fingerprint sensor indicator */ diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt index 3294c816d67c..b45ebd865c55 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt @@ -256,6 +256,7 @@ constructor( it.cancel() null } + mOverlayStateController.setExitAnimationsRunning(false) } private fun blurAnimator( diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt index 297ad847caa6..befd822e14cd 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/TaskFragmentComponent.kt @@ -161,5 +161,6 @@ constructor( TASK_FRAGMENT_TRANSIT_CLOSE, false ) + organizer.unregisterOrganizer() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 2d60fcca58cc..ed8d4263b212 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -42,6 +42,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STR import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.Flags.notifyPowerManagerUserActivityBackground; import static com.android.systemui.Flags.refactorGetCurrentUser; +import static com.android.systemui.Flags.translucentOccludingActivityFix; import static com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel.DREAMING_ANIMATION_DURATION_MS; import android.animation.Animator; @@ -1035,6 +1036,17 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, (int) (fullWidth - initialWidth) /* left */, fullWidth /* right */, mWindowCornerRadius, mWindowCornerRadius); + } else if (translucentOccludingActivityFix() + && mOccludingRemoteAnimationTarget != null + && mOccludingRemoteAnimationTarget.isTranslucent) { + // Animating in a transparent window looks really weird. Just let it be + // fullscreen and the app can do an internal animation if it wants to. + return new TransitionAnimator.State( + 0, + fullHeight, + 0, + fullWidth, + 0f, 0f); } else { final float initialHeight = fullHeight / 2f; final float initialWidth = fullWidth / 2f; @@ -1398,6 +1410,11 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, private final Lazy<DreamViewModel> mDreamViewModel; private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModel; private RemoteAnimationTarget mRemoteAnimationTarget; + + /** + * The most recent RemoteAnimationTarget provided for an occluding activity animation. + */ + private RemoteAnimationTarget mOccludingRemoteAnimationTarget; private boolean mShowCommunalWhenUnoccluding = false; private final Lazy<WindowManagerLockscreenVisibilityManager> mWmLockscreenVisibilityManager; @@ -3930,6 +3947,13 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, public void onAnimationStart(int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { + // Save mRemoteAnimationTarget for reference in the animation controller. Needs to be + // called prior to super.onAnimationStart() since that's the call that eventually asks + // the animation controller to configure the animation state. + if (apps.length > 0) { + mOccludingRemoteAnimationTarget = apps[0]; + } + super.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback); mInteractionJankMonitor.begin( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt index 882f2315f6e8..dd3e6190ceaf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/BiometricSettingsRepository.kt @@ -188,35 +188,33 @@ constructor( ) private val isFingerprintEnrolled: Flow<Boolean> = - selectedUserId - .flatMapLatest { currentUserId -> - conflatedCallbackFlow { - val callback = - object : AuthController.Callback { - override fun onEnrollmentsChanged( - sensorBiometricType: BiometricType, - userId: Int, - hasEnrollments: Boolean - ) { - if (sensorBiometricType.isFingerprint && userId == currentUserId) { - trySendWithFailureLogging( - hasEnrollments, - TAG, - "update fpEnrollment" - ) - } + selectedUserId.flatMapLatest { currentUserId -> + conflatedCallbackFlow { + val callback = + object : AuthController.Callback { + override fun onEnrollmentsChanged( + sensorBiometricType: BiometricType, + userId: Int, + hasEnrollments: Boolean + ) { + if (sensorBiometricType.isFingerprint && userId == currentUserId) { + trySendWithFailureLogging( + hasEnrollments, + TAG, + "update fpEnrollment" + ) } } - authController.addCallback(callback) - awaitClose { authController.removeCallback(callback) } - } + } + authController.addCallback(callback) + trySendWithFailureLogging( + authController.isFingerprintEnrolled(currentUserId), + TAG, + "Initial value of fingerprint enrollment" + ) + awaitClose { authController.removeCallback(callback) } } - .stateIn( - scope, - started = SharingStarted.Eagerly, - initialValue = - authController.isFingerprintEnrolled(userRepository.getSelectedUserInfo().id) - ) + } private val isFaceEnrolled: Flow<Boolean> = selectedUserId.flatMapLatest { selectedUserId: Int -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt index 49d00af61a24..5573f0d5f644 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt @@ -40,6 +40,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest @@ -168,7 +169,9 @@ constructor( keyguardInteractor.isKeyguardGoingAway.filter { it }.map {}, // map to Unit keyguardInteractor.isKeyguardOccluded.flatMapLatest { keyguardOccluded -> if (keyguardOccluded) { - primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled + primaryBouncerInteractor.keyguardAuthenticatedBiometricsHandled.drop( + 1 + ) // drop the initial state } else { emptyFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index 6550937d2db0..f8063c92124d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -86,7 +86,10 @@ constructor( privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION + // Avoid announcing window title. + accessibilityTitle = " " } + private var alternateBouncerView: ConstraintLayout? = null override fun start() { @@ -304,6 +307,7 @@ constructor( } } } + companion object { private const val TAG = "AlternateBouncerViewBinder" private const val swipeTag = "AlternateBouncer-SWIPE" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt index 215ac4662b55..1c6323594c70 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/DeviceEntryIconView.kt @@ -73,14 +73,14 @@ constructor( AccessibilityNodeInfoCompat.ACTION_CLICK, resources.getString(R.string.accessibility_enter_hint) ) + override fun onInitializeAccessibilityNodeInfo( v: View, info: AccessibilityNodeInfo ) { super.onInitializeAccessibilityNodeInfo(v, info) when (accessibilityHintType) { - AccessibilityHintType.BOUNCER -> - info.addAction(accessibilityBouncerHint) + AccessibilityHintType.BOUNCER -> info.addAction(accessibilityBouncerHint) AccessibilityHintType.ENTER -> info.addAction(accessibilityEnterHint) AccessibilityHintType.NONE -> return } @@ -204,12 +204,12 @@ constructor( /* reversible */ false, ) - // LockscreenFingerprint <=> LockscreenLocked + // LockscreenFingerprint => LockscreenLocked animatedIconDrawable.addTransition( R.id.locked_fp, R.id.locked, context.getDrawable(R.drawable.fp_to_locked) as AnimatedVectorDrawable, - /* reversible */ true, + /* reversible */ false, ) // LockscreenUnlocked <=> AodLocked diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt index 4128c529644d..5cf100e78e6e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModel.kt @@ -34,8 +34,8 @@ constructor( alternateBouncerInteractor: AlternateBouncerInteractor, keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { - val canShowAlternateBouncer: Flow<Boolean> = alternateBouncerInteractor.canShowAlternateBouncer - + private val deviceSupportsAlternateBouncer: Flow<Boolean> = + alternateBouncerInteractor.alternateBouncerSupported private val isTransitioningToOrFromOrShowingAlternateBouncer: Flow<Boolean> = keyguardTransitionInteractor .transitionValue(KeyguardState.ALTERNATE_BOUNCER) @@ -43,8 +43,8 @@ constructor( .distinctUntilChanged() val alternateBouncerWindowRequired: Flow<Boolean> = - canShowAlternateBouncer.flatMapLatest { canShowAlternateBouncer -> - if (canShowAlternateBouncer) { + deviceSupportsAlternateBouncer.flatMapLatest { deviceSupportsAlternateBouncer -> + if (deviceSupportsAlternateBouncer) { isTransitioningToOrFromOrShowingAlternateBouncer } else { flowOf(false) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index 46880883ac70..5ce1b5e3dcc5 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -284,9 +284,9 @@ constructor( private fun DeviceEntryIconView.IconType.toAccessibilityHintType(): DeviceEntryIconView.AccessibilityHintType { return when (this) { + DeviceEntryIconView.IconType.FINGERPRINT, DeviceEntryIconView.IconType.LOCK -> DeviceEntryIconView.AccessibilityHintType.BOUNCER DeviceEntryIconView.IconType.UNLOCK -> DeviceEntryIconView.AccessibilityHintType.ENTER - DeviceEntryIconView.IconType.FINGERPRINT, DeviceEntryIconView.IconType.NONE -> DeviceEntryIconView.AccessibilityHintType.NONE } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt new file mode 100644 index 000000000000..6730d2d86d5f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/HeadlessScreenshotHandler.kt @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.screenshot + +import android.net.Uri +import android.os.UserManager +import android.util.Log +import android.view.WindowManager +import com.android.internal.logging.UiEventLogger +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R +import com.google.common.util.concurrent.ListenableFuture +import java.util.UUID +import java.util.concurrent.Executor +import java.util.concurrent.Executors +import java.util.function.Consumer +import javax.inject.Inject + +/** + * A ScreenshotHandler that just saves the screenshot and calls back as appropriate, with no UI. + * + * Basically, ScreenshotController with all the UI bits ripped out. + */ +class HeadlessScreenshotHandler +@Inject +constructor( + private val imageExporter: ImageExporter, + @Main private val mainExecutor: Executor, + private val imageCapture: ImageCapture, + private val userManager: UserManager, + private val uiEventLogger: UiEventLogger, + private val notificationsControllerFactory: ScreenshotNotificationsController.Factory, +) : ScreenshotHandler { + + override fun handleScreenshot( + screenshot: ScreenshotData, + finisher: Consumer<Uri?>, + requestCallback: TakeScreenshotService.RequestCallback + ) { + if (screenshot.type == WindowManager.TAKE_SCREENSHOT_FULLSCREEN) { + screenshot.bitmap = imageCapture.captureDisplay(screenshot.displayId, crop = null) + } + + if (screenshot.bitmap == null) { + Log.e(TAG, "handleScreenshot: Screenshot bitmap was null") + notificationsControllerFactory + .create(screenshot.displayId) + .notifyScreenshotError(R.string.screenshot_failed_to_capture_text) + requestCallback.reportError() + return + } + + val future: ListenableFuture<ImageExporter.Result> = + imageExporter.export( + Executors.newSingleThreadExecutor(), + UUID.randomUUID(), + screenshot.bitmap, + screenshot.getUserOrDefault(), + screenshot.displayId + ) + future.addListener( + { + try { + val result = future.get() + Log.d(TAG, "Saved screenshot: $result") + logScreenshotResultStatus(result.uri, screenshot) + finisher.accept(result.uri) + requestCallback.onFinish() + } catch (e: Exception) { + Log.d(TAG, "Failed to store screenshot", e) + finisher.accept(null) + requestCallback.reportError() + } + }, + mainExecutor + ) + } + + private fun logScreenshotResultStatus(uri: Uri?, screenshot: ScreenshotData) { + if (uri == null) { + uiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, screenshot.packageNameString) + notificationsControllerFactory + .create(screenshot.displayId) + .notifyScreenshotError(R.string.screenshot_failed_to_save_text) + } else { + uiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, screenshot.packageNameString) + if (userManager.isManagedProfile(screenshot.getUserOrDefault().identifier)) { + uiEventLogger.log( + ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, + 0, + screenshot.packageNameString + ) + } + } + } + + companion object { + const val TAG = "HeadlessScreenshotHandler" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt index 2ffb7835f400..5f1688650cb2 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsController.kt @@ -18,6 +18,7 @@ package com.android.systemui.screenshot import android.app.assist.AssistContent import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance +import com.android.systemui.screenshot.ui.viewmodel.PreviewAction import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -84,9 +85,9 @@ constructor( } inner class ActionsCallback(private val screenshotId: UUID) { - fun providePreviewAction(onClick: () -> Unit) { + fun providePreviewAction(previewAction: PreviewAction) { if (screenshotId == currentScreenshotId) { - viewModel.setPreviewAction(onClick) + viewModel.setPreviewAction(previewAction) } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt index b8029c8b1cc3..c216f1da2dc1 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt @@ -29,6 +29,7 @@ import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED import com.android.systemui.screenshot.ui.viewmodel.ActionButtonAppearance +import com.android.systemui.screenshot.ui.viewmodel.PreviewAction import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -40,7 +41,9 @@ import java.util.UUID */ interface ScreenshotActionsProvider { fun onScrollChipReady(onClick: Runnable) + fun onScrollChipInvalidated() + fun setCompletedScreenshot(result: ScreenshotSavedResult) /** @@ -75,17 +78,19 @@ constructor( private var result: ScreenshotSavedResult? = null init { - actionsCallback.providePreviewAction { - debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" } - uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString) - onDeferrableActionTapped { result -> - actionExecutor.startSharedTransition( - createEdit(result.uri, context), - result.user, - true - ) + actionsCallback.providePreviewAction( + PreviewAction(context.resources.getString(R.string.screenshot_edit_description)) { + debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" } + uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString) + onDeferrableActionTapped { result -> + actionExecutor.startSharedTransition( + createEdit(result.uri, context), + result.user, + true + ) + } } - } + ) actionsCallback.provideActionButton( ActionButtonAppearance( diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index e8dfac868546..c87b1f526957 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -101,7 +101,7 @@ import javax.inject.Provider; /** * Controls the state and flow for screenshots. */ -public class ScreenshotController { +public class ScreenshotController implements ScreenshotHandler { private static final String TAG = logTag(ScreenshotController.class); /** @@ -351,7 +351,8 @@ public class ScreenshotController { mShowUIOnExternalDisplay = showUIOnExternalDisplay; } - void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, + @Override + public void handleScreenshot(ScreenshotData screenshot, Consumer<Uri> finisher, RequestCallback requestCallback) { Assert.isMainThread(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt index 40d709d0ab25..6a9615081d72 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.android.systemui.screenshot import android.net.Uri @@ -12,6 +28,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.res.R import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_CAPTURE_FAILED +import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER import com.android.systemui.screenshot.TakeScreenshotService.RequestCallback import java.util.function.Consumer import javax.inject.Inject @@ -25,9 +42,13 @@ interface TakeScreenshotExecutor { onSaved: (Uri?) -> Unit, requestCallback: RequestCallback ) + fun onCloseSystemDialogsReceived() + fun removeWindows() + fun onDestroy() + fun executeScreenshotsAsync( screenshotRequest: ScreenshotRequest, onSaved: Consumer<Uri?>, @@ -35,6 +56,14 @@ interface TakeScreenshotExecutor { ) } +interface ScreenshotHandler { + fun handleScreenshot( + screenshot: ScreenshotData, + finisher: Consumer<Uri?>, + requestCallback: RequestCallback + ) +} + /** * Receives the signal to take a screenshot from [TakeScreenshotService], and calls back with the * result. @@ -51,10 +80,10 @@ constructor( private val screenshotRequestProcessor: ScreenshotRequestProcessor, private val uiEventLogger: UiEventLogger, private val screenshotNotificationControllerFactory: ScreenshotNotificationsController.Factory, + private val headlessScreenshotHandler: HeadlessScreenshotHandler, ) : TakeScreenshotExecutor { - private val displays = displayRepository.displays - private val screenshotControllers = mutableMapOf<Int, ScreenshotController>() + private var screenshotController: ScreenshotController? = null private val notificationControllers = mutableMapOf<Int, ScreenshotNotificationsController>() /** @@ -72,9 +101,15 @@ constructor( val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback) displays.forEach { display -> val displayId = display.displayId + var screenshotHandler: ScreenshotHandler = + if (displayId == Display.DEFAULT_DISPLAY) { + getScreenshotController(display) + } else { + headlessScreenshotHandler + } Log.d(TAG, "Executing screenshot for display $displayId") dispatchToController( - display = display, + screenshotHandler, rawScreenshotData = ScreenshotData.fromRequest(screenshotRequest, displayId), onSaved = if (displayId == Display.DEFAULT_DISPLAY) { @@ -87,7 +122,7 @@ constructor( /** All logging should be triggered only by this method. */ private suspend fun dispatchToController( - display: Display, + screenshotHandler: ScreenshotHandler, rawScreenshotData: ScreenshotData, onSaved: (Uri?) -> Unit, callback: RequestCallback @@ -101,13 +136,12 @@ constructor( logScreenshotRequested(rawScreenshotData) onFailedScreenshotRequest(rawScreenshotData, callback) } - .getOrNull() - ?: return + .getOrNull() ?: return logScreenshotRequested(screenshotData) Log.d(TAG, "Screenshot request: $screenshotData") try { - getScreenshotController(display).handleScreenshot(screenshotData, onSaved, callback) + screenshotHandler.handleScreenshot(screenshotData, onSaved, callback) } catch (e: IllegalStateException) { Log.e(TAG, "Error while ScreenshotController was handling ScreenshotData!", e) onFailedScreenshotRequest(screenshotData, callback) @@ -147,36 +181,24 @@ constructor( } } - /** Propagates the close system dialog signal to all controllers. */ + /** Propagates the close system dialog signal to the ScreenshotController. */ override fun onCloseSystemDialogsReceived() { - screenshotControllers.forEach { (_, screenshotController) -> - if (!screenshotController.isPendingSharedTransition) { - screenshotController.requestDismissal(ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER) - } + if (screenshotController?.isPendingSharedTransition == false) { + screenshotController?.requestDismissal(SCREENSHOT_DISMISSED_OTHER) } } /** Removes all screenshot related windows. */ override fun removeWindows() { - screenshotControllers.forEach { (_, screenshotController) -> - screenshotController.removeWindow() - } + screenshotController?.removeWindow() } /** * Destroys the executor. Afterwards, this class is not expected to work as intended anymore. */ override fun onDestroy() { - screenshotControllers.forEach { (_, screenshotController) -> - screenshotController.onDestroy() - } - screenshotControllers.clear() - } - - private fun getScreenshotController(display: Display): ScreenshotController { - return screenshotControllers.computeIfAbsent(display.displayId) { - screenshotControllerFactory.create(display, /* showUIOnExternalDisplay= */ false) - } + screenshotController?.onDestroy() + screenshotController = null } private fun getNotificationController(id: Int): ScreenshotNotificationsController { @@ -196,6 +218,12 @@ constructor( } } + private fun getScreenshotController(display: Display): ScreenshotController { + val controller = screenshotController ?: screenshotControllerFactory.create(display, false) + screenshotController = controller + return controller + } + /** * Returns a [RequestCallback] that wraps [originalCallback]. * diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt index 4f27b9e4dbf0..b3d5c9e9691c 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/policy/PolicyRequestProcessor.kt @@ -99,7 +99,7 @@ class PolicyRequestProcessor( original, updates.component, updates.owner, - type.displayId + type.displayId, ) } return updated @@ -120,6 +120,7 @@ class PolicyRequestProcessor( return replaceWithScreenshot( original = original, componentName = topMainRootTask?.topActivity ?: defaultComponent, + taskId = topMainRootTask?.taskId, owner = defaultOwner, displayId = original.displayId ) @@ -144,11 +145,12 @@ class PolicyRequestProcessor( ) } - suspend fun replaceWithScreenshot( + private suspend fun replaceWithScreenshot( original: ScreenshotData, componentName: ComponentName?, owner: UserHandle?, displayId: Int, + taskId: Int? = null, ): ScreenshotData { Log.i(TAG, "Capturing screenshot: $componentName / $owner") val screenshot = captureDisplay(displayId) @@ -157,7 +159,8 @@ class PolicyRequestProcessor( bitmap = screenshot, userHandle = owner, topComponent = componentName, - screenBounds = Rect(0, 0, screenshot?.width ?: 0, screenshot?.height ?: 0) + screenBounds = Rect(0, 0, screenshot?.width ?: 0, screenshot?.height ?: 0), + taskId = taskId ?: -1, ) } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt index 442b3873be4d..0fefa0b7757a 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt @@ -128,8 +128,9 @@ constructor(private val buttonViewBinder: ActionButtonViewBinder) { } } launch { - viewModel.previewAction.collect { onClick -> - previewView.setOnClickListener { onClick?.invoke() } + viewModel.previewAction.collect { action -> + previewView.setOnClickListener { action?.onClick?.invoke() } + previewView.contentDescription = action?.contentDescription } } launch { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt index 3f99bc4597cb..25420d44dc79 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/viewmodel/ScreenshotViewModel.kt @@ -31,8 +31,8 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager val scrollingScrim: StateFlow<Bitmap?> = _scrollingScrim private val _badge = MutableStateFlow<Drawable?>(null) val badge: StateFlow<Drawable?> = _badge - private val _previewAction = MutableStateFlow<(() -> Unit)?>(null) - val previewAction: StateFlow<(() -> Unit)?> = _previewAction + private val _previewAction = MutableStateFlow<PreviewAction?>(null) + val previewAction: StateFlow<PreviewAction?> = _previewAction private val _actions = MutableStateFlow(emptyList<ActionButtonViewModel>()) val actions: StateFlow<List<ActionButtonViewModel>> = _actions private val _animationState = MutableStateFlow(AnimationState.NOT_STARTED) @@ -57,8 +57,8 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager _badge.value = badge } - fun setPreviewAction(onClick: () -> Unit) { - _previewAction.value = onClick + fun setPreviewAction(previewAction: PreviewAction) { + _previewAction.value = previewAction } fun addAction( @@ -149,6 +149,11 @@ class ScreenshotViewModel(private val accessibilityManager: AccessibilityManager } } +data class PreviewAction( + val contentDescription: CharSequence, + val onClick: () -> Unit, +) + enum class AnimationState { NOT_STARTED, ENTRANCE_STARTED, // The first 200ms of the entrance animation diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 262befc1e9ad..fff5e104a7ab 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -1770,8 +1770,9 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump // the small clock here // With migrateClocksToBlueprint, weather clock will have behaviors similar to other clocks if (!MigrateClocksToBlueprint.isEnabled()) { + boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled(); if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf() - && hasVisibleNotifications() && isOnAod()) { + && hasVisibleNotifications() && (isOnAod() || bypassEnabled)) { return SMALL; } } @@ -3046,7 +3047,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump if (mDeviceEntryFaceAuthInteractor.canFaceAuthRun()) { mUpdateMonitor.requestActiveUnlock( - ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT, + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT_LEGACY, "lockScreenEmptySpaceTap"); } else { mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java index 4b1ee58c2cc6..02187844d6da 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java @@ -68,7 +68,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; private boolean mIsStatusBarExpanded = false; - private boolean mIsIdleOnGone = false; + private boolean mIsIdleOnGone = true; private boolean mShouldAdjustInsets = false; private View mNotificationShadeWindowView; private View mNotificationPanelView; @@ -282,7 +282,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable { // since we don't want stray touches to go through the light reveal scrim to whatever is // underneath. return mIsStatusBarExpanded - || !mIsIdleOnGone + || (SceneContainerFlag.isEnabled() && !mIsIdleOnGone) || mPrimaryBouncerInteractor.isShowing().getValue() || mAlternateBouncerInteractor.isVisibleState() || mUnlockedScreenOffAnimationController.isAnimationPlaying(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt index ec3af87f2b9b..a7c5f78e5b69 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt @@ -345,32 +345,67 @@ constructor( orElse = flowOf(false), retrySignal = telephonyProcessCrashedEvent, ) - .stateIn(scope, SharingStarted.WhileSubscribed(), false) + .stateIn(scope, SharingStarted.Eagerly, false) private fun satelliteProvisioned(sm: SupportedSatelliteManager): Flow<Boolean> = conflatedCallbackFlow { - val callback = SatelliteProvisionStateCallback { provisioned -> - logBuffer.i { - "onSatelliteProvisionStateChanged: " + - if (provisioned) "provisioned" else "not provisioned" + // TODO(b/347992038): SatelliteManager should be sending the current provisioned + // status when we register a callback. Until then, we have to manually query here. + + // First, check to see what the current status is, and send the result to the output + trySend(queryIsSatelliteProvisioned(sm)) + + val callback = SatelliteProvisionStateCallback { provisioned -> + logBuffer.i { + "onSatelliteProvisionStateChanged: " + + if (provisioned) "provisioned" else "not provisioned" + } + trySend(provisioned) } - trySend(provisioned) - } - var registered = false - try { - sm.registerForProvisionStateChanged( - bgDispatcher.asExecutor(), - callback, - ) - registered = true - } catch (e: Exception) { - logBuffer.e("error registering for provisioning state callback", e) + var registered = false + try { + logBuffer.i { "registerForProvisionStateChanged" } + sm.registerForProvisionStateChanged( + bgDispatcher.asExecutor(), + callback, + ) + registered = true + } catch (e: Exception) { + logBuffer.e("error registering for provisioning state callback", e) + } + + awaitClose { + if (registered) { + sm.unregisterForProvisionStateChanged(callback) + } + } } + .flowOn(bgDispatcher) + + /** Check the current satellite provisioning status. */ + private suspend fun queryIsSatelliteProvisioned(sm: SupportedSatelliteManager): Boolean = + withContext(bgDispatcher) { + suspendCancellableCoroutine { continuation -> + val receiver = + object : OutcomeReceiver<Boolean, SatelliteManager.SatelliteException> { + override fun onResult(result: Boolean) { + logBuffer.i { "requestIsProvisioned.onResult: $result" } + continuation.resume(result) + } - awaitClose { - if (registered) { - sm.unregisterForProvisionStateChanged(callback) + override fun onError(exception: SatelliteManager.SatelliteException) { + logBuffer.e("requestIsProvisioned.onError:", exception) + continuation.resume(false) + } + } + + logBuffer.i { "Query for current satellite provisioned state." } + try { + sm.requestIsProvisioned(bgDispatcher.asExecutor(), receiver) + } catch (e: Exception) { + logBuffer.e("Exception while calling SatelliteManager.requestIsProvisioned:", e) + continuation.resume(false) } } } diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java index aee441a13a5d..c45f98e5f4f5 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java @@ -567,7 +567,7 @@ public class VolumeDialogControllerImpl implements VolumeDialogController, Dumpa streamStateW(stream).levelMax = Math.max(1, getAudioManagerStreamMaxVolume(stream)); updateStreamMuteW(stream, mAudio.isStreamMute(stream)); final StreamState ss = streamStateW(stream); - ss.muteSupported = mAudio.isStreamAffectedByMute(stream); + ss.muteSupported = mAudio.isStreamMutableByUi(stream); ss.name = STREAMS.get(stream); checkRoutedToBluetoothW(stream); } diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt index 7f1faeeb381d..cfcd6b14010d 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractor.kt @@ -16,15 +16,23 @@ package com.android.systemui.volume.panel.component.spatial.domain.interactor +import android.bluetooth.BluetoothProfile import android.media.AudioDeviceAttributes import android.media.AudioDeviceInfo +import android.media.AudioManager +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LocalBluetoothProfile +import com.android.settingslib.flags.Flags import com.android.settingslib.media.domain.interactor.SpatializerInteractor +import com.android.settingslib.volume.data.repository.AudioRepository +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.volume.domain.interactor.AudioOutputInteractor import com.android.systemui.volume.domain.model.AudioOutputDevice import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioAvailabilityModel import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope import javax.inject.Inject +import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted @@ -33,6 +41,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.withContext /** * Provides an ability to access and update spatial audio and head tracking state. @@ -46,6 +55,8 @@ class SpatialAudioComponentInteractor constructor( audioOutputInteractor: AudioOutputInteractor, private val spatializerInteractor: SpatializerInteractor, + private val audioRepository: AudioRepository, + @Background private val backgroundCoroutineContext: CoroutineContext, @VolumePanelScope private val coroutineScope: CoroutineScope, ) { @@ -138,42 +149,85 @@ constructor( } private suspend fun AudioOutputDevice.getAudioDeviceAttributes(): AudioDeviceAttributes? { - when (this) { - is AudioOutputDevice.BuiltIn -> return builtinSpeaker + return when (this) { + is AudioOutputDevice.BuiltIn -> builtinSpeaker is AudioOutputDevice.Bluetooth -> { - return listOf( - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLE_HEADSET, - cachedBluetoothDevice.address, - ), - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLE_SPEAKER, - cachedBluetoothDevice.address, - ), - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLE_BROADCAST, - cachedBluetoothDevice.address, - ), - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, - cachedBluetoothDevice.address, - ), - AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_HEARING_AID, - cachedBluetoothDevice.address, + if (Flags.enableDeterminingSpatialAudioAttributesByProfile()) { + getAudioDeviceAttributesByBluetoothProfile(cachedBluetoothDevice) + } else { + listOf( + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_HEADSET, + cachedBluetoothDevice.address, + ), + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_SPEAKER, + cachedBluetoothDevice.address, + ), + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLE_BROADCAST, + cachedBluetoothDevice.address, + ), + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP, + cachedBluetoothDevice.address, + ), + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_HEARING_AID, + cachedBluetoothDevice.address, + ) ) - ) - .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) } + .firstOrNull { spatializerInteractor.isSpatialAudioAvailable(it) } + } } - else -> return null + else -> null } } + private suspend fun getAudioDeviceAttributesByBluetoothProfile( + cachedBluetoothDevice: CachedBluetoothDevice + ): AudioDeviceAttributes? = + withContext(backgroundCoroutineContext) { + cachedBluetoothDevice.profiles + .firstOrNull { + it.profileId in audioProfiles && it.isEnabled(cachedBluetoothDevice.device) + } + ?.let { profile: LocalBluetoothProfile -> + when (profile.profileId) { + BluetoothProfile.A2DP -> { + AudioDeviceInfo.TYPE_BLUETOOTH_A2DP + } + BluetoothProfile.LE_AUDIO -> { + when ( + audioRepository.getBluetoothAudioDeviceCategory( + cachedBluetoothDevice.address + ) + ) { + AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER -> + AudioDeviceInfo.TYPE_BLE_SPEAKER + else -> AudioDeviceInfo.TYPE_BLE_HEADSET + } + } + BluetoothProfile.HEARING_AID -> { + AudioDeviceInfo.TYPE_HEARING_AID + } + else -> null + } + } + ?.let { + AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + it, + cachedBluetoothDevice.address, + ) + } + } + private companion object { val builtinSpeaker = AudioDeviceAttributes( @@ -181,5 +235,7 @@ constructor( AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "" ) + val audioProfiles = + setOf(BluetoothProfile.A2DP, BluetoothProfile.LE_AUDIO, BluetoothProfile.HEARING_AID) } } diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt index 8858d132c4be..48f6cc4261a9 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt @@ -28,6 +28,7 @@ import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_LEGACY import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE import android.provider.Settings.Secure.ACTIVE_UNLOCK_WAKEUPS_CONSIDERED_UNLOCK_INTENTS @@ -40,6 +41,7 @@ import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.FakeSettings +import dagger.Lazy import java.io.PrintWriter import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -51,7 +53,6 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -import dagger.Lazy @SmallTest class ActiveUnlockConfigTest : SysuiTestCase() { @@ -111,6 +112,48 @@ class ActiveUnlockConfigTest : SysuiTestCase() { ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE ) ) + assertFalse( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT_LEGACY + ) + ) + assertTrue( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT + ) + ) + assertTrue( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL + ) + ) + } + + @Test + fun onUnlockIntentLegacySettingChanged() { + // GIVEN no active unlock settings enabled + assertFalse( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT_LEGACY + ) + ) + + // WHEN unlock on unlock intent legacy is allowed + secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_LEGACY, 1, currentUser) + updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_LEGACY)) + + // THEN active unlock triggers allowed on unlock_intent_legacy, unlock_intent, + // AND biometric fail + assertFalse( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE + ) + ) + assertTrue( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT_LEGACY + ) + ) assertTrue( activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT @@ -132,16 +175,21 @@ class ActiveUnlockConfigTest : SysuiTestCase() { ) ) - // WHEN unlock on biometric failed is allowed + // WHEN unlock on unlock intent is allowed secureSettings.putIntForUser(ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 1, currentUser) updateSetting(secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT)) - // THEN active unlock triggers allowed on: biometric failure ONLY + // THEN active unlock triggers allowed on: unlock intent AND biometric failure assertFalse( activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.WAKE ) ) + assertFalse( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT_LEGACY + ) + ) assertTrue( activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT @@ -184,6 +232,11 @@ class ActiveUnlockConfigTest : SysuiTestCase() { ) assertFalse( activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT_LEGACY + ) + ) + assertFalse( + activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT ) ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 9a99ed7bb512..1e3ee280204b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -27,6 +27,7 @@ import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton import android.hardware.biometrics.PromptInfo import android.hardware.biometrics.PromptVerticalListContentView import android.hardware.face.FaceSensorPropertiesInternal +import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.os.Handler import android.os.IBinder @@ -88,9 +89,8 @@ import org.mockito.Mockito.eq import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify -import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever - +import org.mockito.junit.MockitoJUnit private const val OP_PACKAGE_NAME = "biometric.testapp" @@ -99,33 +99,21 @@ private const val OP_PACKAGE_NAME = "biometric.testapp" @SmallTest open class AuthContainerViewTest : SysuiTestCase() { - @JvmField @Rule - var mockitoRule = MockitoJUnit.rule() - - @Mock - lateinit var callback: AuthDialogCallback - @Mock - lateinit var userManager: UserManager - @Mock - lateinit var lockPatternUtils: LockPatternUtils - @Mock - lateinit var wakefulnessLifecycle: WakefulnessLifecycle - @Mock - lateinit var panelInteractionDetector: AuthDialogPanelInteractionDetector - @Mock - lateinit var windowToken: IBinder - @Mock - lateinit var interactionJankMonitor: InteractionJankMonitor - @Mock - lateinit var vibrator: VibratorHelper - @Mock - lateinit var udfpsUtils: UdfpsUtils - @Mock - lateinit var authController: AuthController - @Mock - lateinit var selectedUserInteractor: SelectedUserInteractor - @Mock - private lateinit var packageManager: PackageManager + @JvmField @Rule var mockitoRule = MockitoJUnit.rule() + + @Mock lateinit var callback: AuthDialogCallback + @Mock lateinit var userManager: UserManager + @Mock lateinit var fingerprintManager: FingerprintManager + @Mock lateinit var lockPatternUtils: LockPatternUtils + @Mock lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock lateinit var panelInteractionDetector: AuthDialogPanelInteractionDetector + @Mock lateinit var windowToken: IBinder + @Mock lateinit var interactionJankMonitor: InteractionJankMonitor + @Mock lateinit var vibrator: VibratorHelper + @Mock lateinit var udfpsUtils: UdfpsUtils + @Mock lateinit var authController: AuthController + @Mock lateinit var selectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var packageManager: PackageManager @Mock private lateinit var activityTaskManager: ActivityTaskManager private lateinit var displayRepository: FakeDisplayRepository @@ -141,11 +129,12 @@ open class AuthContainerViewTest : SysuiTestCase() { private val fingerprintRepository = FakeFingerprintPropertyRepository() private val displayStateRepository = FakeDisplayStateRepository() private val credentialInteractor = FakeCredentialInteractor() - private val bpCredentialInteractor = PromptCredentialInteractor( - Dispatchers.Main.immediate, - biometricPromptRepository, - credentialInteractor, - ) + private val bpCredentialInteractor = + PromptCredentialInteractor( + Dispatchers.Main.immediate, + biometricPromptRepository, + credentialInteractor, + ) private val promptSelectorInteractor by lazy { PromptSelectorInteractorImpl( fingerprintRepository, @@ -166,22 +155,26 @@ open class AuthContainerViewTest : SysuiTestCase() { displayStateInteractor = DisplayStateInteractorImpl( - testScope.backgroundScope, - mContext, - fakeExecutor, - displayStateRepository, - displayRepository, + testScope.backgroundScope, + mContext, + fakeExecutor, + displayStateRepository, + displayRepository, ) udfpsOverlayInteractor = - UdfpsOverlayInteractor( - context, - authController, - selectedUserInteractor, - testScope.backgroundScope, - ) + UdfpsOverlayInteractor( + context, + authController, + selectedUserInteractor, + fingerprintManager, + testScope.backgroundScope, + ) biometricStatusInteractor = - BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository, - fingerprintRepository) + BiometricStatusInteractorImpl( + activityTaskManager, + biometricStatusRepository, + fingerprintRepository + ) iconProvider = IconProvider(context) // Set up default logo icon whenever(packageManager.getApplicationIcon(OP_PACKAGE_NAME)).thenReturn(defaultLogoIcon) @@ -198,10 +191,8 @@ open class AuthContainerViewTest : SysuiTestCase() { @Test fun testNotifiesAnimatedIn() { initializeFingerprintContainer() - verify(callback).onDialogAnimatedIn( - authContainer?.requestId ?: 0L, - true /* startFingerprintNow */ - ) + verify(callback) + .onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */) } @Test @@ -246,10 +237,8 @@ open class AuthContainerViewTest : SysuiTestCase() { waitForIdleSync() // attaching the view resets the state and allows this to happen again - verify(callback).onDialogAnimatedIn( - authContainer?.requestId ?: 0L, - true /* startFingerprintNow */ - ) + verify(callback) + .onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */) } @Test @@ -274,10 +263,8 @@ open class AuthContainerViewTest : SysuiTestCase() { // the first time is triggered by initializeFingerprintContainer() // the second time was triggered by dismissWithoutCallback() - verify(callback, times(2)).onDialogAnimatedIn( - authContainer?.requestId ?: 0L, - true /* startFingerprintNow */ - ) + verify(callback, times(2)) + .onDialogAnimatedIn(authContainer?.requestId ?: 0L, true /* startFingerprintNow */) } @Test @@ -288,18 +275,18 @@ open class AuthContainerViewTest : SysuiTestCase() { verify(panelInteractionDetector).disable() } - @Test fun testActionAuthenticated_sendsDismissedAuthenticated() { val container = initializeFingerprintContainer() container.mBiometricCallback.onAuthenticated() waitForIdleSync() - verify(callback).onDismissed( + verify(callback) + .onDismissed( eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED), eq<ByteArray?>(null), /* credentialAttestation */ eq(authContainer?.requestId ?: 0L) - ) + ) assertThat(container.parent).isNull() } @@ -309,15 +296,17 @@ open class AuthContainerViewTest : SysuiTestCase() { container.mBiometricCallback.onUserCanceled() waitForIdleSync() - verify(callback).onSystemEvent( + verify(callback) + .onSystemEvent( eq(BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL), eq(authContainer?.requestId ?: 0L) - ) - verify(callback).onDismissed( + ) + verify(callback) + .onDismissed( eq(AuthDialogCallback.DISMISSED_USER_CANCELED), eq<ByteArray?>(null), /* credentialAttestation */ eq(authContainer?.requestId ?: 0L) - ) + ) assertThat(container.parent).isNull() } @@ -327,19 +316,21 @@ open class AuthContainerViewTest : SysuiTestCase() { container.mBiometricCallback.onButtonNegative() waitForIdleSync() - verify(callback).onDismissed( + verify(callback) + .onDismissed( eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE), eq<ByteArray?>(null), /* credentialAttestation */ eq(authContainer?.requestId ?: 0L) - ) + ) assertThat(container.parent).isNull() } @Test fun testActionTryAgain_sendsTryAgain() { - val container = initializeFingerprintContainer( - authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK - ) + val container = + initializeFingerprintContainer( + authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK + ) container.mBiometricCallback.onButtonTryAgain() waitForIdleSync() @@ -352,21 +343,24 @@ open class AuthContainerViewTest : SysuiTestCase() { container.mBiometricCallback.onError() waitForIdleSync() - verify(callback).onDismissed( + verify(callback) + .onDismissed( eq(AuthDialogCallback.DISMISSED_ERROR), eq<ByteArray?>(null), /* credentialAttestation */ eq(authContainer?.requestId ?: 0L) - ) + ) assertThat(authContainer!!.parent).isNull() } @Ignore("b/279650412") @Test fun testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() { - val container = initializeFingerprintContainer( - authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or - BiometricManager.Authenticators.DEVICE_CREDENTIAL - ) + val container = + initializeFingerprintContainer( + authenticators = + BiometricManager.Authenticators.BIOMETRIC_WEAK or + BiometricManager.Authenticators.DEVICE_CREDENTIAL + ) container.mBiometricCallback.onUseDeviceCredential() waitForIdleSync() @@ -376,10 +370,12 @@ open class AuthContainerViewTest : SysuiTestCase() { @Test fun testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() { - val container = initializeFingerprintContainer( - authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or - BiometricManager.Authenticators.DEVICE_CREDENTIAL - ) + val container = + initializeFingerprintContainer( + authenticators = + BiometricManager.Authenticators.BIOMETRIC_WEAK or + BiometricManager.Authenticators.DEVICE_CREDENTIAL + ) container.animateToCredentialUI(false) waitForIdleSync() @@ -395,10 +391,12 @@ open class AuthContainerViewTest : SysuiTestCase() { @Test fun testAnimateToCredentialUI_rotateCredentialUI() { - val container = initializeFingerprintContainer( - authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or - BiometricManager.Authenticators.DEVICE_CREDENTIAL - ) + val container = + initializeFingerprintContainer( + authenticators = + BiometricManager.Authenticators.BIOMETRIC_WEAK or + BiometricManager.Authenticators.DEVICE_CREDENTIAL + ) container.animateToCredentialUI(false) waitForIdleSync() @@ -437,13 +435,12 @@ open class AuthContainerViewTest : SysuiTestCase() { mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) var isButtonClicked = false val contentView = - PromptContentViewWithMoreOptionsButton.Builder() - .setMoreOptionsButtonListener( - fakeExecutor) { _, _ -> isButtonClicked = true } - .build() + PromptContentViewWithMoreOptionsButton.Builder() + .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> isButtonClicked = true } + .build() val container = - initializeFingerprintContainer(contentViewWithMoreOptionsButton = contentView) + initializeFingerprintContainer(contentViewWithMoreOptionsButton = contentView) waitForIdleSync() @@ -461,9 +458,9 @@ open class AuthContainerViewTest : SysuiTestCase() { @Test fun testShowCredentialUI_withDescription() { val container = - initializeFingerprintContainer( - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL - ) + initializeFingerprintContainer( + authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL + ) waitForIdleSync() assertThat(container.hasCredentialView()).isTrue() @@ -475,10 +472,10 @@ open class AuthContainerViewTest : SysuiTestCase() { mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) val container = - initializeFingerprintContainer( - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL, - verticalListContentView = PromptVerticalListContentView.Builder().build() - ) + initializeFingerprintContainer( + authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL, + verticalListContentView = PromptVerticalListContentView.Builder().build() + ) // Two-step credential view should show - // 1. biometric prompt without sensor 2. credential view ui waitForIdleSync() @@ -497,14 +494,14 @@ open class AuthContainerViewTest : SysuiTestCase() { mSetFlagsRule.enableFlags(FLAG_CONSTRAINT_BP) mSetFlagsRule.enableFlags(FLAG_CUSTOM_BIOMETRIC_PROMPT) val contentView = - PromptContentViewWithMoreOptionsButton.Builder() - .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> } - .build() + PromptContentViewWithMoreOptionsButton.Builder() + .setMoreOptionsButtonListener(fakeExecutor) { _, _ -> } + .build() val container = - initializeFingerprintContainer( - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL, - contentViewWithMoreOptionsButton = contentView - ) + initializeFingerprintContainer( + authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL, + contentViewWithMoreOptionsButton = contentView + ) waitForIdleSync() assertThat(container.hasCredentialView()).isTrue() @@ -514,13 +511,13 @@ open class AuthContainerViewTest : SysuiTestCase() { @Test fun testCredentialViewUsesEffectiveUserId() { whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(200) - whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200))).thenReturn( - DevicePolicyManager.PASSWORD_QUALITY_SOMETHING - ) + whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200))) + .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING) - val container = initializeFingerprintContainer( - authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL - ) + val container = + initializeFingerprintContainer( + authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL + ) waitForIdleSync() assertThat(container.hasCredentialPatternView()).isTrue() @@ -531,9 +528,8 @@ open class AuthContainerViewTest : SysuiTestCase() { fun testCredentialUI_disablesClickingOnBackground() { val container = initializeCredentialPasswordContainer() assertThat(container.hasBiometricPrompt()).isFalse() - assertThat( - container.findViewById<View>(R.id.background)?.isImportantForAccessibility - ).isFalse() + assertThat(container.findViewById<View>(R.id.background)?.isImportantForAccessibility) + .isFalse() container.findViewById<View>(R.id.background)?.performClick() waitForIdleSync() @@ -552,7 +548,7 @@ open class AuthContainerViewTest : SysuiTestCase() { fun testLayoutParams_hasShowWhenLockedFlag() { val layoutParams = AuthContainerView.getLayoutParams(windowToken, "") assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED) != 0) - .isTrue() + .isTrue() } @Test @@ -590,20 +586,20 @@ open class AuthContainerViewTest : SysuiTestCase() { } private fun initializeCredentialPasswordContainer( - addToView: Boolean = true, + addToView: Boolean = true, ): TestAuthContainerView { whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20) - whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn( - DevicePolicyManager.PASSWORD_QUALITY_NUMERIC - ) + whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))) + .thenReturn(DevicePolicyManager.PASSWORD_QUALITY_NUMERIC) // In the credential view, clicking on the background (to cancel authentication) is not // valid. Thus, the listener should be null, and it should not be in the accessibility // hierarchy. - val container = initializeFingerprintContainer( + val container = + initializeFingerprintContainer( authenticators = BiometricManager.Authenticators.DEVICE_CREDENTIAL, addToView = addToView, - ) + ) waitForIdleSync() assertThat(container.hasCredentialPasswordView()).isTrue() @@ -615,26 +611,28 @@ open class AuthContainerViewTest : SysuiTestCase() { addToView: Boolean = true, verticalListContentView: PromptVerticalListContentView? = null, contentViewWithMoreOptionsButton: PromptContentViewWithMoreOptionsButton? = null, - ) = initializeContainer( - TestAuthContainerView( - authenticators = authenticators, - fingerprintProps = fingerprintSensorPropertiesInternal(), + ) = + initializeContainer( + TestAuthContainerView( + authenticators = authenticators, + fingerprintProps = fingerprintSensorPropertiesInternal(), verticalListContentView = verticalListContentView, - ), - addToView - ) + ), + addToView + ) private fun initializeCoexContainer( authenticators: Int = BiometricManager.Authenticators.BIOMETRIC_WEAK, addToView: Boolean = true - ) = initializeContainer( - TestAuthContainerView( - authenticators = authenticators, - fingerprintProps = fingerprintSensorPropertiesInternal(), - faceProps = faceSensorPropertiesInternal() - ), - addToView - ) + ) = + initializeContainer( + TestAuthContainerView( + authenticators = authenticators, + fingerprintProps = fingerprintSensorPropertiesInternal(), + faceProps = faceSensorPropertiesInternal() + ), + addToView + ) private fun initializeContainer( view: TestAuthContainerView, @@ -655,47 +653,50 @@ open class AuthContainerViewTest : SysuiTestCase() { faceProps: List<FaceSensorPropertiesInternal> = listOf(), verticalListContentView: PromptVerticalListContentView? = null, contentViewWithMoreOptionsButton: PromptContentViewWithMoreOptionsButton? = null, - ) : AuthContainerView( - Config().apply { - mContext = this@AuthContainerViewTest.context - mCallback = callback - mSensorIds = (fingerprintProps.map { it.sensorId } + - faceProps.map { it.sensorId }).toIntArray() - mSkipAnimation = true - mPromptInfo = PromptInfo().apply { - this.authenticators = authenticators - if (verticalListContentView != null) { - this.contentView = verticalListContentView - } else if (contentViewWithMoreOptionsButton != null) { - this.contentView = contentViewWithMoreOptionsButton - } - } - mOpPackageName = OP_PACKAGE_NAME - }, - testScope.backgroundScope, - fingerprintProps, - faceProps, - wakefulnessLifecycle, - panelInteractionDetector, - userManager, - lockPatternUtils, - interactionJankMonitor, - { promptSelectorInteractor }, - PromptViewModel( - displayStateInteractor, - promptSelectorInteractor, - context, - udfpsOverlayInteractor, - biometricStatusInteractor, - udfpsUtils, - iconProvider, - activityTaskManager - ), - { credentialViewModel }, - Handler(TestableLooper.get(this).looper), - fakeExecutor, - vibrator - ) { + ) : + AuthContainerView( + Config().apply { + mContext = this@AuthContainerViewTest.context + mCallback = callback + mSensorIds = + (fingerprintProps.map { it.sensorId } + faceProps.map { it.sensorId }) + .toIntArray() + mSkipAnimation = true + mPromptInfo = + PromptInfo().apply { + this.authenticators = authenticators + if (verticalListContentView != null) { + this.contentView = verticalListContentView + } else if (contentViewWithMoreOptionsButton != null) { + this.contentView = contentViewWithMoreOptionsButton + } + } + mOpPackageName = OP_PACKAGE_NAME + }, + testScope.backgroundScope, + fingerprintProps, + faceProps, + wakefulnessLifecycle, + panelInteractionDetector, + userManager, + lockPatternUtils, + interactionJankMonitor, + { promptSelectorInteractor }, + PromptViewModel( + displayStateInteractor, + promptSelectorInteractor, + context, + udfpsOverlayInteractor, + biometricStatusInteractor, + udfpsUtils, + iconProvider, + activityTaskManager + ), + { credentialViewModel }, + Handler(TestableLooper.get(this).looper), + fakeExecutor, + vibrator + ) { override fun postOnAnimation(runnable: Runnable) { runnable.run() } @@ -717,8 +718,10 @@ open class AuthContainerViewTest : SysuiTestCase() { val layoutParams = AuthContainerView.getLayoutParams(windowToken, "") val lpFlags = layoutParams.flags - assertThat((lpFlags and WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) - != 0).isTrue() + assertThat( + (lpFlags and WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) != 0 + ) + .isTrue() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt index 3d63c5b6d0f8..13f2c7212e36 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.biometrics.domain.interactor import android.graphics.Rect +import android.hardware.fingerprint.FingerprintManager import android.view.MotionEvent import android.view.Surface import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -51,6 +52,7 @@ class UdfpsOverlayInteractorTest : SysuiTestCase() { private lateinit var testScope: TestScope + @Mock private lateinit var fingerprintManager: FingerprintManager @Mock private lateinit var authController: AuthController @Captor private lateinit var authControllerCallback: ArgumentCaptor<AuthController.Callback> @@ -111,6 +113,7 @@ class UdfpsOverlayInteractorTest : SysuiTestCase() { context, authController, selectedUserInteractor, + fingerprintManager, testScope.backgroundScope ) testScope.runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt index 42382540d401..7fa165c19f60 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt @@ -73,6 +73,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoRule +import org.mockito.kotlin.argumentCaptor @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -218,6 +219,13 @@ class SideFpsOverlayViewBinderTest : SysuiTestCase() { verify(kosmos.windowManager).addView(any(), any()) + var viewCaptor = argumentCaptor<View>() + verify(kosmos.windowManager).addView(viewCaptor.capture(), any()) + verify(viewCaptor.firstValue) + .announceForAccessibility( + mContext.getText(R.string.accessibility_side_fingerprint_indicator_label) + ) + // Hide alternate bouncer kosmos.keyguardBouncerRepository.setAlternateVisible(false) runCurrent() diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt index 93c6d9ee732c..314743c042c2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt @@ -36,6 +36,7 @@ import android.hardware.biometrics.PromptContentViewWithMoreOptionsButton import android.hardware.biometrics.PromptInfo import android.hardware.biometrics.PromptVerticalListContentView import android.hardware.face.FaceSensorPropertiesInternal +import android.hardware.fingerprint.FingerprintManager import android.hardware.fingerprint.FingerprintSensorProperties import android.hardware.fingerprint.FingerprintSensorPropertiesInternal import android.platform.test.annotations.EnableFlags @@ -114,6 +115,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa @JvmField @Rule var mockitoRule = MockitoJUnit.rule() @Mock private lateinit var lockPatternUtils: LockPatternUtils + @Mock private lateinit var fingerprintManager: FingerprintManager @Mock private lateinit var authController: AuthController @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor @Mock private lateinit var udfpsUtils: UdfpsUtils @@ -202,6 +204,7 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa context, authController, selectedUserInteractor, + fingerprintManager, testScope.backgroundScope ) biometricStatusRepository = FakeBiometricStatusRepository() diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt index 6398a5af52f1..c1bd37811787 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt @@ -19,26 +19,22 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.biometrics.data.repository.fakeFingerprintPropertyRepository -import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.kosmos.testScope -import com.android.systemui.statusbar.policy.keyguardStateController import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -import org.mockito.kotlin.whenever +import org.junit.runners.JUnit4 @ExperimentalCoroutinesApi @RunWith(AndroidJUnit4::class) @@ -54,35 +50,13 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { fun alternateBouncerTransition_alternateBouncerWindowRequiredTrue() = testScope.runTest { mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - val canShowAlternateBouncer by collectLastValue(underTest.canShowAlternateBouncer) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) - givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsUdfps() transitionRepository.sendTransitionSteps( listOf( - stepToLockscreen(0f, TransitionState.STARTED), - stepToLockscreen(.4f), - stepToLockscreen(1f, TransitionState.FINISHED), - ), - testScope, - ) - assertThat(canShowAlternateBouncer).isTrue() - transitionRepository.sendTransitionSteps( - listOf( - stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED), - stepFromLockscreenToAlternateBouncer(.4f), - stepFromLockscreenToAlternateBouncer(.6f), - ), - testScope, - ) - assertThat(canShowAlternateBouncer).isTrue() - assertThat(alternateBouncerWindowRequired).isTrue() - - transitionRepository.sendTransitionSteps( - listOf( stepFromAlternateBouncer(0f, TransitionState.STARTED), - stepFromAlternateBouncer(.2f), + stepFromAlternateBouncer(.4f), stepFromAlternateBouncer(.6f), ), testScope, @@ -104,21 +78,13 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) - givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsUdfps() transitionRepository.sendTransitionSteps( listOf( - stepToLockscreen(0f, TransitionState.STARTED), - stepToLockscreen(.4f), - stepToLockscreen(1f, TransitionState.FINISHED), - ), - testScope, - ) - transitionRepository.sendTransitionSteps( - listOf( - stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED), - stepFromLockscreenToAlternateBouncer(.4f), - stepFromLockscreenToAlternateBouncer(.6f), + stepFromAlternateBouncer(0f, TransitionState.STARTED), + stepFromAlternateBouncer(.4f), + stepFromAlternateBouncer(.6f), + stepFromAlternateBouncer(1f), ), testScope, ) @@ -131,23 +97,13 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) - givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsUdfps() transitionRepository.sendTransitionSteps( listOf( - stepFromLockscreenToDozing(0f, TransitionState.STARTED), - stepFromLockscreenToDozing(.4f), - stepFromLockscreenToDozing(.6f), - stepFromLockscreenToDozing(1f, TransitionState.FINISHED), - ), - testScope, - ) - assertThat(alternateBouncerWindowRequired).isFalse() - transitionRepository.sendTransitionSteps( - listOf( stepFromDozingToLockscreen(0f, TransitionState.STARTED), stepFromDozingToLockscreen(.4f), stepFromDozingToLockscreen(.6f), + stepFromDozingToLockscreen(1f), ), testScope, ) @@ -160,39 +116,19 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) - givenCanShowAlternateBouncer() fingerprintPropertyRepository.supportsRearFps() transitionRepository.sendTransitionSteps( listOf( - stepToLockscreen(0f, TransitionState.STARTED), - stepToLockscreen(.4f), - stepToLockscreen(1f, TransitionState.FINISHED), - ), - testScope, - ) - transitionRepository.sendTransitionSteps( - listOf( - stepFromLockscreenToAlternateBouncer(0f, TransitionState.STARTED), - stepFromLockscreenToAlternateBouncer(.4f), - stepFromLockscreenToAlternateBouncer(.6f), + stepFromAlternateBouncer(0f, TransitionState.STARTED), + stepFromAlternateBouncer(.4f), + stepFromAlternateBouncer(.6f), + stepFromAlternateBouncer(1f), ), testScope, ) assertThat(alternateBouncerWindowRequired).isFalse() } - private fun stepToLockscreen( - value: Float, - state: TransitionState = TransitionState.RUNNING - ): TransitionStep { - return step( - from = KeyguardState.GONE, - to = KeyguardState.LOCKSCREEN, - value = value, - transitionState = state, - ) - } - private fun stepFromAlternateBouncer( value: Float, state: TransitionState = TransitionState.RUNNING @@ -205,18 +141,6 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { ) } - private fun stepFromLockscreenToAlternateBouncer( - value: Float, - state: TransitionState = TransitionState.RUNNING - ): TransitionStep { - return step( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.ALTERNATE_BOUNCER, - value = value, - transitionState = state, - ) - } - private fun stepFromDozingToLockscreen( value: Float, state: TransitionState = TransitionState.RUNNING @@ -229,18 +153,6 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { ) } - private fun stepFromLockscreenToDozing( - value: Float, - state: TransitionState = TransitionState.RUNNING - ): TransitionStep { - return step( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.DOZING, - value = value, - transitionState = state, - ) - } - private fun step( from: KeyguardState, to: KeyguardState, @@ -255,16 +167,4 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { ownerName = "AlternateBouncerViewModelTest" ) } - - /** - * Given the alternate bouncer parameters are set so that the alternate bouncer can show, aside - * from the fingerprint modality. - */ - private fun givenCanShowAlternateBouncer() { - kosmos.fakeKeyguardBouncerRepository.setPrimaryShow(false) - kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) - kosmos.fakeBiometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true) - whenever(kosmos.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) - whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(false) - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt index 6f5c56eb9148..148a2e56f2a5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt @@ -24,6 +24,7 @@ import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.screenshot.ui.viewmodel.PreviewAction import com.google.common.truth.Truth.assertThat import java.util.UUID import kotlin.test.Test @@ -60,9 +61,9 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { fun previewActionAccessed_beforeScreenshotCompleted_doesNothing() { actionsProvider = createActionsProvider() - val previewActionCaptor = argumentCaptor<() -> Unit>() + val previewActionCaptor = argumentCaptor<PreviewAction>() verify(actionsCallback).providePreviewAction(previewActionCaptor.capture()) - previewActionCaptor.firstValue.invoke() + previewActionCaptor.firstValue.onClick.invoke() verifyNoMoreInteractions(actionExecutor) } @@ -102,14 +103,14 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { fun actionAccessed_whilePending_launchesMostRecentAction() = runTest { actionsProvider = createActionsProvider() - val previewActionCaptor = argumentCaptor<() -> Unit>() + val previewActionCaptor = argumentCaptor<PreviewAction>() verify(actionsCallback).providePreviewAction(previewActionCaptor.capture()) val actionButtonCaptor = argumentCaptor<() -> Unit>() verify(actionsCallback, times(2)) .provideActionButton(any(), any(), actionButtonCaptor.capture()) actionButtonCaptor.firstValue.invoke() - previewActionCaptor.firstValue.invoke() + previewActionCaptor.firstValue.onClick.invoke() actionButtonCaptor.secondValue.invoke() actionsProvider.setCompletedScreenshot(validResult) diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt index 2a3c31aee6e7..a27c449fb86f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotActionsControllerTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.screenshot import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.screenshot.ui.viewmodel.PreviewAction import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import java.util.UUID import kotlin.test.Test @@ -35,7 +36,7 @@ class ScreenshotActionsControllerTest : SysuiTestCase() { private val screenshotData = mock<ScreenshotData>() private val actionExecutor = mock<ActionExecutor>() private val viewModel = mock<ScreenshotViewModel>() - private val onClick = mock<() -> Unit>() + private val previewAction = PreviewAction("description", onClick = {}) private lateinit var actionsController: ScreenshotActionsController private lateinit var fakeActionsProvider1: FakeActionsProvider @@ -43,6 +44,7 @@ class ScreenshotActionsControllerTest : SysuiTestCase() { private val actionsProviderFactory = object : ScreenshotActionsProvider.Factory { var isFirstCall = true + override fun create( requestId: UUID, request: ScreenshotData, @@ -69,16 +71,16 @@ class ScreenshotActionsControllerTest : SysuiTestCase() { @Test fun setPreview_onCurrentScreenshot_updatesViewModel() { actionsController.setCurrentScreenshot(screenshotData) - fakeActionsProvider1.callPreview(onClick) + fakeActionsProvider1.callPreview(previewAction) - verify(viewModel).setPreviewAction(onClick) + verify(viewModel).setPreviewAction(previewAction) } @Test fun setPreview_onNonCurrentScreenshot_doesNotUpdateViewModel() { actionsController.setCurrentScreenshot(screenshotData) actionsController.setCurrentScreenshot(screenshotData) - fakeActionsProvider1.callPreview(onClick) + fakeActionsProvider1.callPreview(previewAction) verify(viewModel, never()).setPreviewAction(any()) } @@ -87,8 +89,8 @@ class ScreenshotActionsControllerTest : SysuiTestCase() { private val actionsCallback: ScreenshotActionsController.ActionsCallback ) : ScreenshotActionsProvider { - fun callPreview(onClick: () -> Unit) { - actionsCallback.providePreviewAction(onClick) + fun callPreview(previewAction: PreviewAction) { + actionsCallback.providePreviewAction(previewAction) } override fun onScrollChipReady(onClick: Runnable) {} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt index bf7d909380df..aca63f4a9e02 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt @@ -22,7 +22,6 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.kotlinArgumentCaptor as ArgumentCaptor import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import java.lang.IllegalStateException @@ -43,8 +42,7 @@ import org.mockito.Mockito.verifyNoMoreInteractions @SmallTest class TakeScreenshotExecutorTest : SysuiTestCase() { - private val controller0 = mock<ScreenshotController>() - private val controller1 = mock<ScreenshotController>() + private val controller = mock<ScreenshotController>() private val notificationsController0 = mock<ScreenshotNotificationsController>() private val notificationsController1 = mock<ScreenshotNotificationsController>() private val controllerFactory = mock<ScreenshotController.Factory>() @@ -56,6 +54,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { private val topComponent = ComponentName(mContext, TakeScreenshotExecutorTest::class.java) private val testScope = TestScope(UnconfinedTestDispatcher()) private val eventLogger = UiEventLoggerFake() + private val headlessHandler = mock<HeadlessScreenshotHandler>() private val screenshotExecutor = TakeScreenshotExecutorImpl( @@ -64,14 +63,13 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { testScope, requestProcessor, eventLogger, - notificationControllerFactory + notificationControllerFactory, + headlessHandler, ) @Before fun setUp() { - whenever(controllerFactory.create(any(), any())).thenAnswer { - if (it.getArgument<Display>(0).displayId == 0) controller0 else controller1 - } + whenever(controllerFactory.create(any(), any())).thenReturn(controller) whenever(notificationControllerFactory.create(eq(0))).thenReturn(notificationsController0) whenever(notificationControllerFactory.create(eq(1))).thenReturn(notificationsController1) } @@ -86,14 +84,14 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) verify(controllerFactory).create(eq(internalDisplay), any()) - verify(controllerFactory).create(eq(externalDisplay), any()) + verify(controllerFactory, never()).create(eq(externalDisplay), any()) val capturer = ArgumentCaptor<ScreenshotData>() - verify(controller0).handleScreenshot(capturer.capture(), any(), any()) + verify(controller).handleScreenshot(capturer.capture(), any(), any()) assertThat(capturer.value.displayId).isEqualTo(0) // OnSaved callback should be different. - verify(controller1).handleScreenshot(capturer.capture(), any(), any()) + verify(headlessHandler).handleScreenshot(capturer.capture(), any(), any()) assertThat(capturer.value.displayId).isEqualTo(1) assertThat(eventLogger.numLogs()).isEqualTo(2) @@ -125,10 +123,10 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val capturer = ArgumentCaptor<ScreenshotData>() - verify(controller0).handleScreenshot(capturer.capture(), any(), any()) + verify(controller).handleScreenshot(capturer.capture(), any(), any()) assertThat(capturer.value.displayId).isEqualTo(0) // OnSaved callback should be different. - verify(controller1, never()).handleScreenshot(any(), any(), any()) + verify(headlessHandler, never()).handleScreenshot(any(), any(), any()) assertThat(eventLogger.numLogs()).isEqualTo(1) assertThat(eventLogger.get(0).eventId) @@ -146,13 +144,14 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) verifyNoMoreInteractions(controllerFactory) + verify(headlessHandler, never()).handleScreenshot(any(), any(), any()) screenshotExecutor.onDestroy() } @Test fun executeScreenshots_allowedTypes_allCaptured() = testScope.runTest { - whenever(controllerFactory.create(any(), any())).thenReturn(controller0) + whenever(controllerFactory.create(any(), any())).thenReturn(controller) setDisplays( display(TYPE_INTERNAL, id = 0), @@ -163,7 +162,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) - verify(controller0, times(4)).handleScreenshot(any(), any(), any()) + verify(controller, times(1)).handleScreenshot(any(), any(), any()) + verify(headlessHandler, times(3)).handleScreenshot(any(), any(), any()) screenshotExecutor.onDestroy() } @@ -177,8 +177,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() - verify(controller0).handleScreenshot(any(), any(), capturer0.capture()) - verify(controller1).handleScreenshot(any(), any(), capturer1.capture()) + verify(controller).handleScreenshot(any(), any(), capturer0.capture()) + verify(headlessHandler).handleScreenshot(any(), any(), capturer1.capture()) verify(callback, never()).onFinish() @@ -202,8 +202,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() - verify(controller0).handleScreenshot(any(), any(), capturer0.capture()) - verify(controller1).handleScreenshot(any(), nullable(), capturer1.capture()) + verify(controller).handleScreenshot(any(), any(), capturer0.capture()) + verify(headlessHandler).handleScreenshot(any(), any(), capturer1.capture()) verify(callback, never()).onFinish() @@ -229,8 +229,8 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { val capturer0 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() val capturer1 = ArgumentCaptor<TakeScreenshotService.RequestCallback>() - verify(controller0).handleScreenshot(any(), any(), capturer0.capture()) - verify(controller1).handleScreenshot(any(), any(), capturer1.capture()) + verify(controller).handleScreenshot(any(), any(), capturer0.capture()) + verify(headlessHandler).handleScreenshot(any(), any(), capturer1.capture()) verify(callback, never()).onFinish() @@ -254,50 +254,45 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.onDestroy() - verify(controller0).onDestroy() - verify(controller1).onDestroy() + verify(controller).onDestroy() } @Test - fun removeWindows_propagatedToControllers() = + fun removeWindows_propagatedToController() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.removeWindows() - verify(controller0).removeWindow() - verify(controller1).removeWindow() + verify(controller).removeWindow() screenshotExecutor.onDestroy() } @Test - fun onCloseSystemDialogsReceived_propagatedToControllers() = + fun onCloseSystemDialogsReceived_propagatedToController() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.onCloseSystemDialogsReceived() - verify(controller0).requestDismissal(any()) - verify(controller1).requestDismissal(any()) + verify(controller).requestDismissal(any()) screenshotExecutor.onDestroy() } @Test - fun onCloseSystemDialogsReceived_someControllerHavePendingTransitions() = + fun onCloseSystemDialogsReceived_controllerHasPendingTransitions() = testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) - whenever(controller0.isPendingSharedTransition).thenReturn(true) - whenever(controller1.isPendingSharedTransition).thenReturn(false) + whenever(controller.isPendingSharedTransition).thenReturn(true) val onSaved = { _: Uri? -> } screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) screenshotExecutor.onCloseSystemDialogsReceived() - verify(controller0, never()).requestDismissal(any()) - verify(controller1).requestDismissal(any()) + verify(controller, never()).requestDismissal(any()) screenshotExecutor.onDestroy() } @@ -317,7 +312,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { .isEqualTo(ScreenshotData.fromRequest(screenshotRequest)) val capturer = ArgumentCaptor<ScreenshotData>() - verify(controller0).handleScreenshot(capturer.capture(), any(), any()) + verify(controller).handleScreenshot(capturer.capture(), any(), any()) assertThat(capturer.value).isEqualTo(toBeReturnedByProcessor) screenshotExecutor.onDestroy() @@ -388,9 +383,9 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) val onSaved = { _: Uri? -> } - whenever(controller0.handleScreenshot(any(), any(), any())) + whenever(controller.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) - whenever(controller1.handleScreenshot(any(), any(), any())) + whenever(headlessHandler.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) @@ -408,9 +403,9 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) val onSaved = { _: Uri? -> } - whenever(controller0.handleScreenshot(any(), any(), any())) + whenever(controller.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) - whenever(controller1.handleScreenshot(any(), any(), any())) + whenever(headlessHandler.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) @@ -428,9 +423,9 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { testScope.runTest { setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1)) val onSaved = { _: Uri? -> } - whenever(controller0.handleScreenshot(any(), any(), any())) + whenever(controller.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) - whenever(controller1.handleScreenshot(any(), any(), any())) + whenever(headlessHandler.handleScreenshot(any(), any(), any())) .thenThrow(IllegalStateException::class.java) screenshotExecutor.executeScreenshots(createScreenshotRequest(), onSaved, callback) @@ -449,7 +444,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { assertThat(it).isNull() onSavedCallCount += 1 } - whenever(controller0.handleScreenshot(any(), any(), any())).thenAnswer { + whenever(controller.handleScreenshot(any(), any(), any())).thenAnswer { (it.getArgument(1) as Consumer<Uri?>).accept(null) } @@ -478,6 +473,7 @@ class TakeScreenshotExecutorTest : SysuiTestCase() { var processed: ScreenshotData? = null var toReturn: ScreenshotData? = null var shouldThrowException = false + override suspend fun process(screenshot: ScreenshotData): ScreenshotData { if (shouldThrowException) throw RequestProcessorException("") processed = screenshot diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt index 4945ace3b88c..c5708287a967 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/policy/PolicyRequestProcessorTest.kt @@ -22,6 +22,7 @@ import android.os.UserHandle import android.view.Display.DEFAULT_DISPLAY import android.view.WindowManager.ScreenshotSource.SCREENSHOT_KEY_CHORD import android.view.WindowManager.TAKE_SCREENSHOT_FULLSCREEN +import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.systemui.screenshot.ImageCapture import com.android.systemui.screenshot.ScreenshotData import com.android.systemui.screenshot.data.model.DisplayContentScenarios.ActivityNames.FILES @@ -37,20 +38,23 @@ import org.junit.Test class PolicyRequestProcessorTest { - val imageCapture = object : ImageCapture { - override fun captureDisplay(displayId: Int, crop: Rect?) = null - override suspend fun captureTask(taskId: Int) = null - } + val imageCapture = + object : ImageCapture { + override fun captureDisplay(displayId: Int, crop: Rect?) = null + + override suspend fun captureTask(taskId: Int) = null + } /** Tests behavior when no policies are applied */ @Test fun testProcess_defaultOwner_whenNoPolicyApplied() { val fullScreenWork = DisplayContentRepository { - singleFullScreen(TaskSpec(taskId = 1001, name = FILES, userId = WORK)) + singleFullScreen(TaskSpec(taskId = TASK_ID, name = FILES, userId = WORK)) } val request = - ScreenshotData(TAKE_SCREENSHOT_FULLSCREEN, + ScreenshotData( + TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_CHORD, null, topComponent = null, @@ -58,24 +62,34 @@ class PolicyRequestProcessorTest { taskId = -1, insets = Insets.NONE, bitmap = null, - displayId = DEFAULT_DISPLAY) + displayId = DEFAULT_DISPLAY + ) /* Create a policy request processor with no capture policies */ val requestProcessor = - PolicyRequestProcessor(Dispatchers.Unconfined, + PolicyRequestProcessor( + Dispatchers.Unconfined, imageCapture, policies = emptyList(), defaultOwner = UserHandle.of(PERSONAL), defaultComponent = ComponentName("default", "Component"), - displayTasks = fullScreenWork) + displayTasks = fullScreenWork + ) val result = runBlocking { requestProcessor.process(request) } - assertWithMessage( - "With no policy, the screenshot should be assigned to the default user" - ).that(result.userHandle).isEqualTo(UserHandle.of(PERSONAL)) + assertWithMessage("With no policy, the screenshot should be assigned to the default user") + .that(result.userHandle) + .isEqualTo(UserHandle.of(PERSONAL)) + + assertWithMessage("The topComponent of the screenshot") + .that(result.topComponent) + .isEqualTo(ComponentName.unflattenFromString(FILES)) + + assertWithMessage("Task ID").that(result.taskId).isEqualTo(TASK_ID) + } - assertWithMessage("The topComponent of the screenshot").that(result.topComponent) - .isEqualTo(ComponentName.unflattenFromString(FILES)) + companion object { + const val TASK_ID = 1001 } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt index 0dd988d424b9..40f7b7fb9968 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shared/notifications/data/repository/NotificationSettingsRepositoryTest.kt @@ -56,7 +56,7 @@ class NotificationSettingsRepositoryTest : SysuiTestCase() { @Test fun testGetIsShowNotificationsOnLockscreenEnabled() = testScope.runTest { - val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled) + val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled()) secureSettingsRepository.setInt( name = Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, @@ -74,7 +74,7 @@ class NotificationSettingsRepositoryTest : SysuiTestCase() { @Test fun testSetIsShowNotificationsOnLockscreenEnabled() = testScope.runTest { - val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled) + val showNotifs by collectLastValue(underTest.isShowNotificationsOnLockScreenEnabled()) underTest.setShowNotificationsOnLockscreenEnabled(true) assertThat(showNotifs).isEqualTo(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt index 890a2e40c94c..f7ff568332ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt @@ -32,6 +32,7 @@ import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_NOT_CO import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_OFF import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNAVAILABLE import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN +import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_ERROR import android.telephony.satellite.SatelliteManager.SatelliteException import android.telephony.satellite.SatelliteModemStateCallback import android.telephony.satellite.SatelliteProvisionStateCallback @@ -62,6 +63,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.mockito.Mock import org.mockito.Mockito +import org.mockito.Mockito.atLeastOnce import org.mockito.Mockito.doAnswer import org.mockito.Mockito.never import org.mockito.Mockito.times @@ -354,13 +356,142 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { } @Test - fun satelliteProvisioned_supported_tracksCallback() = + fun satelliteProvisioned_returnsException_defaultsToFalse() = + testScope.runTest { + // GIVEN satellite is supported on device + doAnswer { + val callback: OutcomeReceiver<Boolean, SatelliteException> = + it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException> + callback.onResult(true) + } + .whenever(satelliteManager) + .requestIsSupported(any(), any()) + + // GIVEN satellite returns an error when asked if provisioned + doAnswer { + val receiver = it.arguments[1] as OutcomeReceiver<Boolean, SatelliteException> + receiver.onError(SatelliteException(SATELLITE_RESULT_ERROR)) + null + } + .whenever(satelliteManager) + .requestIsProvisioned( + any(), + any<OutcomeReceiver<Boolean, SatelliteException>>() + ) + + // GIVEN we've been up long enough to start querying + systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME) + + underTest = + DeviceBasedSatelliteRepositoryImpl( + Optional.of(satelliteManager), + telephonyManager, + dispatcher, + testScope.backgroundScope, + logBuffer = FakeLogBuffer.Factory.create(), + verboseLogBuffer = FakeLogBuffer.Factory.create(), + systemClock, + ) + + // WHEN we try to check for provisioned status + val provisioned by collectLastValue(underTest.isSatelliteProvisioned) + + // THEN well, first we don't throw... + // AND THEN we assume that it's not provisioned + assertThat(provisioned).isFalse() + } + + @Test + fun satelliteProvisioned_throwsWhenQuerying_defaultsToFalse() = + testScope.runTest { + // GIVEN satellite is supported on device + doAnswer { + val callback: OutcomeReceiver<Boolean, SatelliteException> = + it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException> + callback.onResult(true) + } + .whenever(satelliteManager) + .requestIsSupported(any(), any()) + + // GIVEN satellite throws when asked if provisioned + whenever(satelliteManager.requestIsProvisioned(any(), any())) + .thenThrow(SecurityException()) + + // GIVEN we've been up long enough to start querying + systemClock.setUptimeMillis(Process.getStartUptimeMillis() + MIN_UPTIME) + + underTest = + DeviceBasedSatelliteRepositoryImpl( + Optional.of(satelliteManager), + telephonyManager, + dispatcher, + testScope.backgroundScope, + logBuffer = FakeLogBuffer.Factory.create(), + verboseLogBuffer = FakeLogBuffer.Factory.create(), + systemClock, + ) + + // WHEN we try to check for provisioned status + val provisioned by collectLastValue(underTest.isSatelliteProvisioned) + + // THEN well, first we don't throw... + // AND THEN we assume that it's not provisioned + assertThat(provisioned).isFalse() + } + + @Test + fun satelliteProvisioned_supported_provisioned_queriesInitialStateBeforeCallbacks() = + testScope.runTest { + // GIVEN satellite is supported, and provisioned + setUpRepo( + uptime = MIN_UPTIME, + satMan = satelliteManager, + satelliteSupported = true, + initialSatelliteIsProvisioned = true, + ) + + val provisioned by collectLastValue(underTest.isSatelliteProvisioned) + + runCurrent() + + // THEN the current state is requested + verify(satelliteManager, atLeastOnce()).requestIsProvisioned(any(), any()) + + // AND the state is correct + assertThat(provisioned).isTrue() + } + + @Test + fun satelliteProvisioned_supported_notProvisioned_queriesInitialStateBeforeCallbacks() = + testScope.runTest { + // GIVEN satellite is supported, and provisioned + setUpRepo( + uptime = MIN_UPTIME, + satMan = satelliteManager, + satelliteSupported = true, + initialSatelliteIsProvisioned = false, + ) + + val provisioned by collectLastValue(underTest.isSatelliteProvisioned) + + runCurrent() + + // THEN the current state is requested + verify(satelliteManager, atLeastOnce()).requestIsProvisioned(any(), any()) + + // AND the state is correct + assertThat(provisioned).isFalse() + } + + @Test + fun satelliteProvisioned_supported_notInitiallyProvisioned_tracksCallback() = testScope.runTest { // GIVEN satellite is not supported setUpRepo( uptime = MIN_UPTIME, satMan = satelliteManager, satelliteSupported = true, + initialSatelliteIsProvisioned = false, ) val provisioned by collectLastValue(underTest.isSatelliteProvisioned) @@ -416,6 +547,8 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { // THEN listeners are re-registered verify(satelliteManager, times(2)).registerForProvisionStateChanged(any(), any()) + // AND the state is queried again + verify(satelliteManager, times(2)).requestIsProvisioned(any(), any()) } @Test @@ -632,6 +765,7 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { uptime: Long = MIN_UPTIME, satMan: SatelliteManager? = satelliteManager, satelliteSupported: Boolean = true, + initialSatelliteIsProvisioned: Boolean = true, ) { doAnswer { val callback: OutcomeReceiver<Boolean, SatelliteException> = @@ -641,6 +775,14 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { .whenever(satelliteManager) .requestIsSupported(any(), any()) + doAnswer { + val callback: OutcomeReceiver<Boolean, SatelliteException> = + it.getArgument(1) as OutcomeReceiver<Boolean, SatelliteException> + callback.onResult(initialSatelliteIsProvisioned) + } + .whenever(satelliteManager) + .requestIsProvisioned(any(), any()) + systemClock.setUptimeMillis(Process.getStartUptimeMillis() + uptime) underTest = diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt index 9dae44dc8053..7c53639a85a6 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/TestMocksModule.kt @@ -18,6 +18,7 @@ package com.android.systemui import android.app.ActivityManager import android.app.admin.DevicePolicyManager import android.app.trust.TrustManager +import android.hardware.fingerprint.FingerprintManager import android.os.UserManager import android.service.notification.NotificationListenerService import android.util.DisplayMetrics @@ -94,6 +95,7 @@ data class TestMocksModule( @get:Provides val deviceProvisionedController: DeviceProvisionedController = mock(), @get:Provides val dozeParameters: DozeParameters = mock(), @get:Provides val dumpManager: DumpManager = mock(), + @get:Provides val fingerprintManager: FingerprintManager = mock(), @get:Provides val headsUpManager: HeadsUpManager = mock(), @get:Provides val guestResumeSessionReceiver: GuestResumeSessionReceiver = mock(), @get:Provides val keyguardBypassController: KeyguardBypassController = mock(), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt index cbfc7686a896..ae592b968f8b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorKosmos.kt @@ -17,17 +17,20 @@ package com.android.systemui.biometrics.domain.interactor import android.content.applicationContext +import android.hardware.fingerprint.FingerprintManager import com.android.systemui.biometrics.authController import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.user.domain.interactor.selectedUserInteractor +import com.android.systemui.util.mockito.mock val Kosmos.udfpsOverlayInteractor by Fixture { UdfpsOverlayInteractor( context = applicationContext, authController = authController, selectedUserInteractor = selectedUserInteractor, + fingerprintManager = mock<FingerprintManager>(), scope = applicationCoroutineScope, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt index 21d59f09711c..135cb14a3497 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioRepository.kt @@ -22,7 +22,6 @@ import com.android.settingslib.volume.data.repository.AudioRepository import com.android.settingslib.volume.shared.model.AudioStream import com.android.settingslib.volume.shared.model.AudioStreamModel import com.android.settingslib.volume.shared.model.RingerMode -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -42,6 +41,7 @@ class FakeAudioRepository : AudioRepository { private val models: MutableMap<AudioStream, MutableStateFlow<AudioStreamModel>> = mutableMapOf() private val lastAudibleVolumes: MutableMap<AudioStream, Int> = mutableMapOf() + private val deviceCategories: MutableMap<String, Int> = mutableMapOf() private fun getAudioStreamModelState( audioStream: AudioStream @@ -60,7 +60,7 @@ class FakeAudioRepository : AudioRepository { ) } - override fun getAudioStream(audioStream: AudioStream): Flow<AudioStreamModel> = + override fun getAudioStream(audioStream: AudioStream): StateFlow<AudioStreamModel> = getAudioStreamModelState(audioStream).asStateFlow() override suspend fun setVolume(audioStream: AudioStream, volume: Int) { @@ -103,4 +103,12 @@ class FakeAudioRepository : AudioRepository { override suspend fun setRingerMode(audioStream: AudioStream, mode: RingerMode) { mutableRingerMode.value = mode } + + fun setBluetoothAudioDeviceCategory(bluetoothAddress: String, category: Int) { + deviceCategories[bluetoothAddress] = category + } + + override suspend fun getBluetoothAudioDeviceCategory(bluetoothAddress: String): Int { + return deviceCategories[bluetoothAddress] ?: AudioManager.AUDIO_DEVICE_CATEGORY_UNKNOWN + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt index 141f2426f365..83adc799b471 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/TestMediaDevicesFactory.kt @@ -18,9 +18,11 @@ package com.android.systemui.volume.panel.component.mediaoutput.domain.interacto import android.annotation.SuppressLint import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothProfile import android.graphics.drawable.Drawable import android.graphics.drawable.TestStubDrawable import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.settingslib.bluetooth.LeAudioProfile import com.android.settingslib.media.BluetoothMediaDevice import com.android.settingslib.media.MediaDevice import com.android.settingslib.media.PhoneMediaDevice @@ -59,11 +61,17 @@ object TestMediaDevicesFactory { whenever(name).thenReturn(deviceName) whenever(address).thenReturn(deviceAddress) } + val leAudioProfile = + mock<LeAudioProfile> { + whenever(profileId).thenReturn(BluetoothProfile.LE_AUDIO) + whenever(isEnabled(bluetoothDevice)).thenReturn(true) + } val cachedBluetoothDevice: CachedBluetoothDevice = mock { whenever(isHearingAidDevice).thenReturn(true) whenever(address).thenReturn(deviceAddress) whenever(device).thenReturn(bluetoothDevice) whenever(name).thenReturn(deviceName) + whenever(profiles).thenReturn(listOf(leAudioProfile)) } return mock<BluetoothMediaDevice> { whenever(name).thenReturn(deviceName) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt index 95a7b9bb185b..6a46d56ba55a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/spatial/domain/interactor/SpatialAudioComponentInteractorKosmos.kt @@ -17,8 +17,10 @@ package com.android.systemui.volume.panel.component.spatial.domain.interactor import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.backgroundCoroutineContext import com.android.systemui.kosmos.testScope import com.android.systemui.media.spatializerInteractor +import com.android.systemui.volume.data.repository.audioRepository import com.android.systemui.volume.domain.interactor.audioOutputInteractor val Kosmos.spatialAudioComponentInteractor by @@ -26,6 +28,8 @@ val Kosmos.spatialAudioComponentInteractor by SpatialAudioComponentInteractor( audioOutputInteractor, spatializerInteractor, + audioRepository, + backgroundCoroutineContext, testScope.backgroundScope, ) } diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt index fec6ff17a608..0458f535c1eb 100644 --- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt +++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/RotationChangeProvider.kt @@ -28,6 +28,9 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.atomic.AtomicInteger + +private const val INVALID_ROTATION = -1 /** * Allows to subscribe to rotation changes. Updates are provided for the display associated to @@ -45,7 +48,7 @@ constructor( private val listeners = CopyOnWriteArrayList<RotationListener>() private val displayListener = RotationDisplayListener() - private var lastRotation: Int? = null + private val lastRotation = AtomicInteger(INVALID_ROTATION) override fun addCallback(listener: RotationListener) { bgHandler.post { @@ -61,7 +64,7 @@ constructor( listeners -= listener if (listeners.isEmpty()) { unsubscribeToRotation() - lastRotation = null + lastRotation.set(INVALID_ROTATION) } } } @@ -100,9 +103,8 @@ constructor( if (displayId == display.displayId) { val currentRotation = display.rotation - if (lastRotation == null || lastRotation != currentRotation) { + if (lastRotation.compareAndSet(lastRotation.get(), currentRotation)) { listeners.forEach { it.onRotationChanged(currentRotation) } - lastRotation = currentRotation } } } finally { diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java index d7da2f0052d3..443bb745ad84 100644 --- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java +++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java @@ -685,15 +685,6 @@ public final class PresentationStatsEventLogger { } /** - * Set how many views are filtered from fill because they are not in current session - */ - public void maybeSetFilteredFillableViewsCount(int filteredViewsCount) { - mEventInternal.ifPresent(event -> { - event.mFilteredFillabaleViewCount = filteredViewsCount; - }); - } - - /** * Set views_filled_failure_count using failure count as long as mEventInternal * presents. */ @@ -789,7 +780,6 @@ public final class PresentationStatsEventLogger { + " mAppPackageUid=" + mCallingAppUid + " mIsCredentialRequest=" + event.mIsCredentialRequest + " mWebviewRequestedCredential=" + event.mWebviewRequestedCredential - + " mFilteredFillabaleViewCount=" + event.mFilteredFillabaleViewCount + " mViewFillableTotalCount=" + event.mViewFillableTotalCount + " mViewFillFailureCount=" + event.mViewFillFailureCount + " mFocusedId=" + event.mFocusedId @@ -846,7 +836,6 @@ public final class PresentationStatsEventLogger { mCallingAppUid, event.mIsCredentialRequest, event.mWebviewRequestedCredential, - event.mFilteredFillabaleViewCount, event.mViewFillableTotalCount, event.mViewFillFailureCount, event.mFocusedId, @@ -895,7 +884,6 @@ public final class PresentationStatsEventLogger { int mFieldClassificationRequestId = DEFAULT_VALUE_INT; boolean mIsCredentialRequest = false; boolean mWebviewRequestedCredential = false; - int mFilteredFillabaleViewCount = DEFAULT_VALUE_INT; int mViewFillableTotalCount = DEFAULT_VALUE_INT; int mViewFillFailureCount = DEFAULT_VALUE_INT; int mFocusedId = DEFAULT_VALUE_INT; diff --git a/services/autofill/java/com/android/server/autofill/RequestId.java b/services/autofill/java/com/android/server/autofill/RequestId.java index 29ad786dbd4b..d8069a840156 100644 --- a/services/autofill/java/com/android/server/autofill/RequestId.java +++ b/services/autofill/java/com/android/server/autofill/RequestId.java @@ -16,8 +16,14 @@ package com.android.server.autofill; -import java.util.List; +import static com.android.server.autofill.Helper.sDebug; + +import android.util.Slog; +import android.util.SparseArray; + import java.util.concurrent.atomic.AtomicInteger; +import java.util.List; +import java.util.Random; // Helper class containing various methods to deal with FillRequest Ids. // For authentication flows, there needs to be a way to know whether to retrieve the Fill @@ -25,56 +31,97 @@ import java.util.concurrent.atomic.AtomicInteger; // way to achieve this is by assigning odd number request ids to secondary provider and // even numbers to primary provider. public class RequestId { + private AtomicInteger sIdCounter; + + // The minimum request id is 2 to avoid possible authentication issues. + static final int MIN_REQUEST_ID = 2; + // The maximum request id is 0x7FFF to make sure the 16th bit is 0. + // This is to make sure the authentication id is always positive. + static final int MAX_REQUEST_ID = 0x7FFF; // 32767 + + // The maximum start id is made small to best avoid wrapping around. + static final int MAX_START_ID = 1000; + // The magic number is used to determine if a wrap has happened. + // The underlying assumption of MAGIC_NUMBER is that there can't be as many as MAGIC_NUMBER + // of fill requests in one session. so there can't be as many as MAGIC_NUMBER of fill requests + // getting dropped. + static final int MAGIC_NUMBER = 5000; + + static final int MIN_PRIMARY_REQUEST_ID = 2; + static final int MAX_PRIMARY_REQUEST_ID = 0x7FFE; // 32766 + + static final int MIN_SECONDARY_REQUEST_ID = 3; + static final int MAX_SECONDARY_REQUEST_ID = 0x7FFF; // 32767 + + private static final String TAG = "RequestId"; + + // WARNING: This constructor should only be used for testing + RequestId(int startId) { + if (startId < MIN_REQUEST_ID || startId > MAX_REQUEST_ID) { + throw new IllegalArgumentException("startId must be between " + MIN_REQUEST_ID + + " and " + MAX_REQUEST_ID); + } + if (sDebug) { + Slog.d(TAG, "RequestId(int): startId= " + startId); + } + sIdCounter = new AtomicInteger(startId); + } - private AtomicInteger sIdCounter; - - // Mainly used for tests - RequestId(int start) { - sIdCounter = new AtomicInteger(start); - } - - public RequestId() { - this((int) (Math.floor(Math.random() * 0xFFFF))); - } - - public static int getLastRequestIdIndex(List<Integer> requestIds) { - int lastId = -1; - int indexOfBiggest = -1; - // Biggest number is usually the latest request, since IDs only increase - // The only exception is when the request ID wraps around back to 0 - for (int i = requestIds.size() - 1; i >= 0; i--) { - if (requestIds.get(i) > lastId) { - lastId = requestIds.get(i); - indexOfBiggest = i; - } + // WARNING: This get method should only be used for testing + int getRequestId() { + return sIdCounter.get(); } - // 0xFFFE + 2 == 0x1 (for secondary) - // 0xFFFD + 2 == 0x0 (for primary) - // Wrap has occurred - if (lastId >= 0xFFFD) { - // Calculate the biggest size possible - // If list only has one kind of request ids - we need to multiple by 2 - // (since they skip odd ints) - // Also subtract one from size because at least one integer exists pre-wrap - int calcSize = (requestIds.size()) * 2; - //Biggest possible id after wrapping - int biggestPossible = (lastId + calcSize) % 0xFFFF; - lastId = -1; - indexOfBiggest = -1; - for (int i = 0; i < requestIds.size(); i++) { - int currentId = requestIds.get(i); - if (currentId <= biggestPossible && currentId > lastId) { - lastId = currentId; - indexOfBiggest = i; + public RequestId() { + Random random = new Random(); + int low = MIN_REQUEST_ID; + int high = MAX_START_ID + 1; // nextInt is exclusive on upper limit + + // Generate a random start request id that >= MIN_REQUEST_ID and <= MAX_START_ID + int startId = random.nextInt(high - low) + low; + if (sDebug) { + Slog.d(TAG, "RequestId(): startId= " + startId); } - } + sIdCounter = new AtomicInteger(startId); } - return indexOfBiggest; - } + // Given a list of request ids, find the index of the last request id. + // Note: Since the request id wraps around, the largest request id may not be + // the latest request id. + // + // @param requestIds List of request ids in ascending order with at least one element. + // @return Index of the last request id. + public static int getLastRequestIdIndex(List<Integer> requestIds) { + // If there is only one request id, return index as 0. + if (requestIds.size() == 1) { + return 0; + } + + // We have to use a magical number to determine if a wrap has happened because + // the request id could be lost. The underlying assumption of MAGIC_NUMBER is that + // there can't be as many as MAGIC_NUMBER of fill requests in one session. + boolean wrapHasHappened = false; + int latestRequestIdIndex = -1; + + for (int i = 0; i < requestIds.size() - 1; i++) { + if (requestIds.get(i+1) - requestIds.get(i) > MAGIC_NUMBER) { + wrapHasHappened = true; + latestRequestIdIndex = i; + break; + } + } + + // If there was no wrap, the last request index is the last index. + if (!wrapHasHappened) { + latestRequestIdIndex = requestIds.size() - 1; + } + if (sDebug) { + Slog.d(TAG, "getLastRequestIdIndex(): latestRequestIdIndex = " + latestRequestIdIndex); + } + return latestRequestIdIndex; + } - public int nextId(boolean isSecondary) { + public int nextId(boolean isSecondary) { // For authentication flows, there needs to be a way to know whether to retrieve the Fill // Response from the primary provider or the secondary provider from the requestId. A simple // way to achieve this is by assigning odd number request ids to secondary provider and @@ -82,13 +129,20 @@ public class RequestId { int requestId; do { - requestId = sIdCounter.incrementAndGet() % 0xFFFF; + requestId = sIdCounter.incrementAndGet() % (MAX_REQUEST_ID + 1); + // Skip numbers smaller than MIN_REQUEST_ID to avoid possible authentication issue + if (requestId < MIN_REQUEST_ID) { + requestId = MIN_REQUEST_ID; + } sIdCounter.set(requestId); } while (isSecondaryProvider(requestId) != isSecondary); + if (sDebug) { + Slog.d(TAG, "nextId(): requestId = " + requestId); + } return requestId; - } + } - public static boolean isSecondaryProvider(int requestId) { - return requestId % 2 == 1; - } + public static boolean isSecondaryProvider(int requestId) { + return requestId % 2 == 1; + } } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 494e956c413f..4852478281d8 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -6641,8 +6641,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState boolean waitingDatasetAuth = false; boolean hideHighlight = (entryCount == 1 && dataset.getFieldIds().get(0).equals(mCurrentViewId)); - // Count how many views are filtered because they are not in current session - int numOfViewsFiltered = 0; for (int i = 0; i < entryCount; i++) { if (dataset.getFieldValues().get(i) == null) { continue; @@ -6655,7 +6653,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.v(TAG, "Skipping filling view: " + viewId + " as it isn't part of the current session: " + id); } - numOfViewsFiltered += 1; continue; } ids.add(viewId); @@ -6669,8 +6666,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState viewState.resetState(ViewState.STATE_WAITING_DATASET_AUTH); } } - mPresentationStatsEventLogger.maybeSetFilteredFillableViewsCount( - numOfViewsFiltered); if (!ids.isEmpty()) { if (waitingDatasetAuth) { mUi.hideFillUi(this); @@ -6902,17 +6897,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING; } + // Return latest response index in mResponses SparseArray. @GuardedBy("mLock") private int getLastResponseIndexLocked() { - if (mResponses != null) { - List<Integer> requestIdList = new ArrayList<>(); - final int responseCount = mResponses.size(); - for (int i = 0; i < responseCount; i++) { - requestIdList.add(mResponses.keyAt(i)); - } - return mRequestId.getLastRequestIdIndex(requestIdList); + if (mResponses == null || mResponses.size() == 0) { + return -1; + } + List<Integer> requestIdList = new ArrayList<>(); + final int responseCount = mResponses.size(); + for (int i = 0; i < responseCount; i++) { + requestIdList.add(mResponses.keyAt(i)); } - return -1; + return mRequestId.getLastRequestIdIndex(requestIdList); } private LogMaker newLogMaker(int category) { diff --git a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java index 4860a274cfa8..8abbe5666d58 100644 --- a/services/backup/java/com/android/server/backup/utils/TarBackupReader.java +++ b/services/backup/java/com/android/server/backup/utils/TarBackupReader.java @@ -792,10 +792,11 @@ public class TarBackupReader { } private String getVToUAllowlist(Context context, int userId) { - return Settings.Secure.getStringForUser( + String allowlist = Settings.Secure.getStringForUser( context.getContentResolver(), Settings.Secure.V_TO_U_RESTORE_ALLOWLIST, userId); + return (allowlist == null) ? "" : allowlist; } private static long extractRadix(byte[] data, int offset, int maxChars, int radix) diff --git a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java index b64aa8a1cbc9..ea6351baf597 100644 --- a/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java +++ b/services/contextualsearch/java/com/android/server/contextualsearch/ContextualSearchManagerService.java @@ -364,7 +364,7 @@ public class ContextualSearchManagerService extends SystemService { } @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) - private int invokeContextualSearchIntent(Intent launchIntent) { + private int invokeContextualSearchIntent(Intent launchIntent, final int userId) { // Contextual search starts with a frozen screen - so we launch without // any system animations or starting window. final ActivityOptions opts = ActivityOptions.makeCustomTaskAnimation(mContext, @@ -372,7 +372,7 @@ public class ContextualSearchManagerService extends SystemService { opts.setDisableStartingWindow(true); return mAtmInternal.startActivityWithScreenshot(launchIntent, mContext.getPackageName(), Binder.getCallingUid(), Binder.getCallingPid(), null, - opts.toBundle(), Binder.getCallingUserHandle().getIdentifier()); + opts.toBundle(), userId); } private void enforcePermission(@NonNull final String func) { @@ -446,6 +446,8 @@ public class ContextualSearchManagerService extends SystemService { synchronized (this) { if (DEBUG_USER) Log.d(TAG, "startContextualSearch"); enforcePermission("startContextualSearch"); + final int callingUserId = Binder.getCallingUserHandle().getIdentifier(); + mAssistDataRequester.cancel(); // Creates a new CallbackToken at mToken and an expiration handler. issueToken(); @@ -455,7 +457,7 @@ public class ContextualSearchManagerService extends SystemService { Binder.withCleanCallingIdentity(() -> { Intent launchIntent = getContextualSearchIntent(entrypoint, mToken); if (launchIntent != null) { - int result = invokeContextualSearchIntent(launchIntent); + int result = invokeContextualSearchIntent(launchIntent, callingUserId); if (DEBUG_USER) Log.d(TAG, "Launch result: " + result); } }); diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index 44aea15b2bde..e2ab0d9f2683 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -1603,7 +1603,7 @@ public class SystemConfig { } else { mPackageToSharedUidAllowList.put(pkgName, sharedUid); } - } + } break; case "asl-file": { String packageName = parser.getAttributeValue(null, "package"); String path = parser.getAttributeValue(null, "path"); diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 1bb792227798..f61bd6040658 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -2790,8 +2790,9 @@ public class AppOpsService extends IAppOpsService.Stub { * have information on them. */ private static boolean isOpAllowedForUid(int uid) { + int appId = UserHandle.getAppId(uid); return Flags.runtimePermissionAppopsMappingEnabled() - && (uid == Process.ROOT_UID || uid == Process.SYSTEM_UID); + && (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID); } @Override diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 0b3b1112e2ed..8e29de773a77 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -2799,12 +2799,13 @@ public class AudioDeviceBroker { return mDeviceInventory.getImmutableDeviceInventory(); } - void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) { - mDeviceInventory.addOrUpdateDeviceSAStateInInventory(deviceState); + void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState, boolean syncInventory) { + mDeviceInventory.addOrUpdateDeviceSAStateInInventory(deviceState, syncInventory); } - void addOrUpdateBtAudioDeviceCategoryInInventory(AdiDeviceState deviceState) { - mDeviceInventory.addOrUpdateAudioDeviceCategoryInInventory(deviceState); + void addOrUpdateBtAudioDeviceCategoryInInventory( + AdiDeviceState deviceState, boolean syncInventory) { + mDeviceInventory.addOrUpdateAudioDeviceCategoryInInventory(deviceState, syncInventory); } @Nullable diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index ba7aee05159a..6ff4a617cec4 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -135,9 +135,10 @@ public class AudioDeviceInventory { * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. * @param deviceState the device to update */ - void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState) { + void addOrUpdateDeviceSAStateInInventory(AdiDeviceState deviceState, boolean syncInventory) { synchronized (mDeviceInventoryLock) { - mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, (oldState, newState) -> { + mDeviceInventory.merge(deviceState.getDeviceId(), deviceState, + (oldState, newState) -> { oldState.setHasHeadTracker(newState.hasHeadTracker()); oldState.setHeadTrackerEnabled(newState.isHeadTrackerEnabled()); oldState.setSAEnabled(newState.isSAEnabled()); @@ -145,7 +146,9 @@ public class AudioDeviceInventory { }); checkDeviceInventorySize_l(); } - mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); + if (syncInventory) { + mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); + } } /** @@ -196,7 +199,8 @@ public class AudioDeviceInventory { * AdiDeviceState in the {@link AudioDeviceInventory#mDeviceInventory} list. * @param deviceState the device to update */ - void addOrUpdateAudioDeviceCategoryInInventory(AdiDeviceState deviceState) { + void addOrUpdateAudioDeviceCategoryInInventory( + AdiDeviceState deviceState, boolean syncInventory) { AtomicBoolean updatedCategory = new AtomicBoolean(false); synchronized (mDeviceInventoryLock) { if (automaticBtDeviceType()) { @@ -218,7 +222,9 @@ public class AudioDeviceInventory { if (updatedCategory.get()) { mDeviceBroker.postUpdatedAdiDeviceState(deviceState, false /*initSA*/); } - mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); + if (syncInventory) { + mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); + } } void addAudioDeviceWithCategoryInInventoryIfNeeded(@NonNull String address, @@ -235,14 +241,14 @@ public class AudioDeviceInventory { boolean bleCategoryFound = false; AdiDeviceState deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLE_HEADSET); if (deviceState != null) { - addOrUpdateAudioDeviceCategoryInInventory(deviceState); + addOrUpdateAudioDeviceCategoryInInventory(deviceState, true /*syncInventory*/); btCategory = deviceState.getAudioDeviceCategory(); bleCategoryFound = true; } deviceState = findBtDeviceStateForAddress(address, DEVICE_OUT_BLUETOOTH_A2DP); if (deviceState != null) { - addOrUpdateAudioDeviceCategoryInInventory(deviceState); + addOrUpdateAudioDeviceCategoryInInventory(deviceState, true /*syncInventory*/); int a2dpCategory = deviceState.getAudioDeviceCategory(); if (bleCategoryFound && a2dpCategory != btCategory) { Log.w(TAG, "Found different audio device category for A2DP and BLE profiles with " @@ -269,23 +275,43 @@ public class AudioDeviceInventory { } /** - * synchronize AdiDeviceState for LE devices in the same group + * Synchronize AdiDeviceState for LE devices in the same group + * or BT classic devices with the same address. + * @param updatedDevice the device state to synchronize or null. + * Called with null once after the device inventory and spatializer helper + * have been initialized to resync all devices. */ void onSynchronizeAdiDevicesInInventory(AdiDeviceState updatedDevice) { synchronized (mDevicesLock) { synchronized (mDeviceInventoryLock) { - boolean found = false; - found |= synchronizeBleDeviceInInventory(updatedDevice); - if (automaticBtDeviceType()) { - found |= synchronizeDeviceProfilesInInventory(updatedDevice); - } - if (found) { - mDeviceBroker.postPersistAudioDeviceSettings(); + if (updatedDevice != null) { + onSynchronizeAdiDeviceInInventory_l(updatedDevice); + } else { + for (AdiDeviceState ads : mDeviceInventory.values()) { + onSynchronizeAdiDeviceInInventory_l(ads); + } } } } } + /** + * Synchronize AdiDeviceState for LE devices in the same group + * or BT classic devices with the same address. + * @param updatedDevice the device state to synchronize. + */ + @GuardedBy({"mDevicesLock", "mDeviceInventoryLock"}) + void onSynchronizeAdiDeviceInInventory_l(AdiDeviceState updatedDevice) { + boolean found = false; + found |= synchronizeBleDeviceInInventory(updatedDevice); + if (automaticBtDeviceType()) { + found |= synchronizeDeviceProfilesInInventory(updatedDevice); + } + if (found) { + mDeviceBroker.postPersistAudioDeviceSettings(); + } + } + @GuardedBy("mDeviceInventoryLock") private void checkDeviceInventorySize_l() { if (mDeviceInventory.size() > MAX_DEVICE_INVENTORY_ENTRIES) { @@ -595,6 +621,9 @@ public class AudioDeviceInventory { mDeviceName = TextUtils.emptyIfNull(deviceName); mDeviceAddress = TextUtils.emptyIfNull(address); mDeviceIdentityAddress = TextUtils.emptyIfNull(identityAddress); + if (mDeviceIdentityAddress.isEmpty()) { + mDeviceIdentityAddress = mDeviceAddress; + } mDeviceCodecFormat = codecFormat; mGroupId = groupId; mPeerDeviceAddress = TextUtils.emptyIfNull(peerAddress); @@ -2951,8 +2980,8 @@ public class AudioDeviceInventory { // Note if the device is not compatible with spatialization mode or the device // type is not canonical, it will be ignored in {@link SpatializerHelper}. if (devState != null) { - addOrUpdateDeviceSAStateInInventory(devState); - addOrUpdateAudioDeviceCategoryInInventory(devState); + addOrUpdateDeviceSAStateInInventory(devState, false /*syncInventory*/); + addOrUpdateAudioDeviceCategoryInInventory(devState, false /*syncInventory*/); } } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 64452a979edd..b0590fe70232 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -707,10 +707,14 @@ public class AudioService extends IAudioService.Stub // Streams currently muted by ringer mode and dnd protected static volatile int sRingerAndZenModeMutedStreams; - /** Streams that can be muted. Do not resolve to aliases when checking. + /** Streams that can be muted by system. Do not resolve to aliases when checking. * @see System#MUTE_STREAMS_AFFECTED */ private int mMuteAffectedStreams; + /** Streams that can be muted by user. Do not resolve to aliases when checking. + * @see System#MUTE_STREAMS_AFFECTED */ + private int mUserMutableStreams; + @NonNull private SoundEffectsHelper mSfxHelper; @@ -2343,6 +2347,7 @@ public class AudioService extends IAudioService.Stub mMuteAffectedStreams &= ~(1 << vss.mStreamType); } } + updateUserMutableStreams(); } private void createStreamStates() { @@ -2412,6 +2417,8 @@ public class AudioService extends IAudioService.Stub } pw.print("\n- mute affected streams = 0x"); pw.println(Integer.toHexString(mMuteAffectedStreams)); + pw.print("\n- user mutable streams = 0x"); + pw.println(Integer.toHexString(mUserMutableStreams)); } private void updateStreamVolumeAlias(boolean updateVolumes, String caller) { @@ -2906,6 +2913,7 @@ public class AudioService extends IAudioService.Stub mMuteAffectedStreams = mSettings.getSystemIntForUser(cr, System.MUTE_STREAMS_AFFECTED, AudioSystem.DEFAULT_MUTE_STREAMS_AFFECTED, UserHandle.USER_CURRENT); + updateUserMutableStreams(); updateMasterMono(cr); @@ -2925,6 +2933,12 @@ public class AudioService extends IAudioService.Stub mVolumeController.loadSettings(cr); } + private void updateUserMutableStreams() { + mUserMutableStreams = mMuteAffectedStreams; + mUserMutableStreams &= ~(1 << AudioSystem.STREAM_VOICE_CALL); + mUserMutableStreams &= ~(1 << AudioSystem.STREAM_BLUETOOTH_SCO); + } + @GuardedBy("mSettingsLock") private void resetActiveAssistantUidsLocked() { mActiveAssistantServiceUids = NO_ACTIVE_ASSISTANT_SERVICE_UIDS; @@ -7133,6 +7147,11 @@ public class AudioService extends IAudioService.Stub return (mMuteAffectedStreams & (1 << streamType)) != 0; } + @Override + public boolean isStreamMutableByUi(int streamType) { + return (mUserMutableStreams & (1 << streamType)) != 0; + } + private void ensureValidDirection(int direction) { switch (direction) { case AudioManager.ADJUST_LOWER: @@ -9644,6 +9663,9 @@ public class AudioService extends IAudioService.Stub case MSG_INIT_SPATIALIZER: onInitSpatializer(); + // the device inventory can only be synchronized after the + // spatializer has been initialized + mDeviceBroker.postSynchronizeAdiDevicesInInventory(null); mAudioEventWakeLock.release(); break; @@ -11399,7 +11421,8 @@ public class AudioService extends IAudioService.Stub deviceState.setAudioDeviceCategory(btAudioDeviceCategory); - mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory(deviceState); + mDeviceBroker.addOrUpdateBtAudioDeviceCategoryInInventory( + deviceState, true /*syncInventory*/); mDeviceBroker.postPersistAudioDeviceSettings(); mSpatializerHelper.refreshDevice(deviceState.getAudioDeviceAttributes(), diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index cae169550d9a..9265ff2d9b2d 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -568,7 +568,8 @@ public class SpatializerHelper { updatedDevice = new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), ada.getAddress()); initSAState(updatedDevice); - mDeviceBroker.addOrUpdateDeviceSAStateInInventory(updatedDevice); + mDeviceBroker.addOrUpdateDeviceSAStateInInventory( + updatedDevice, true /*syncInventory*/); } if (updatedDevice != null) { onRoutingUpdated(); @@ -723,7 +724,7 @@ public class SpatializerHelper { new AdiDeviceState(canonicalDeviceType, ada.getInternalType(), ada.getAddress()); initSAState(deviceState); - mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState); + mDeviceBroker.addOrUpdateDeviceSAStateInInventory(deviceState, true /*syncInventory*/); mDeviceBroker.postPersistAudioDeviceSettings(); logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later. } diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java index d061e2d21811..fbd32a67fe6c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java @@ -31,7 +31,6 @@ import android.util.Slog; import com.android.server.biometrics.log.BiometricContext; import com.android.server.biometrics.log.BiometricLogger; -import com.android.server.biometrics.sensors.fingerprint.aidl.AidlSession; import java.util.function.Supplier; @@ -203,16 +202,6 @@ public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implement } } - // TODO(b/317414324): Deprecate setIgnoreDisplayTouches - protected final void resetIgnoreDisplayTouches() { - final AidlSession session = (AidlSession) getFreshDaemon(); - try { - session.getSession().setIgnoreDisplayTouches(false); - } catch (RemoteException e) { - Slog.e(TAG, "Remote exception when resetting setIgnoreDisplayTouches"); - } - } - @Override public boolean isInterruptable() { return true; diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java index 4c86f57a190b..60cfd5a5a6ae 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java @@ -962,6 +962,19 @@ public class FingerprintService extends SystemService { provider.onUdfpsUiEvent(event, requestId, sensorId); } + @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) + @Override + public void setIgnoreDisplayTouches(long requestId, int sensorId, boolean ignoreTouches) { + super.setIgnoreDisplayTouches_enforcePermission(); + + final ServiceProvider provider = mRegistry.getProviderForSensor(sensorId); + if (provider == null) { + Slog.w(TAG, + "No matching provider for setIgnoreDisplayTouches, sensorId: " + sensorId); + return; + } + provider.setIgnoreDisplayTouches(requestId, sensorId, ignoreTouches); + } @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL) @Override diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java index a6cf2f4b31ae..e4a99e6be72c 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java @@ -134,6 +134,8 @@ public interface ServiceProvider extends void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller); + void setIgnoreDisplayTouches(long requestId, int sensorId, boolean ignoreTouches); + void onPowerPressed(); @NonNull diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java index dce0175ca593..15d7a476b345 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/Udfps.java @@ -31,4 +31,5 @@ public interface Udfps { void onPointerUp(PointerContext pc); void onUdfpsUiEvent(@FingerprintManager.UdfpsUiEvent int event); boolean isPointerDown(); + void setIgnoreDisplayTouches(boolean ignoreTouches); } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java index 72d92b974c1a..d04afdb72913 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java @@ -33,7 +33,6 @@ import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcqu import android.hardware.biometrics.BiometricManager.Authenticators; import android.hardware.biometrics.BiometricSourceType; import android.hardware.biometrics.common.ICancellationSignal; -import android.hardware.biometrics.common.OperationState; import android.hardware.biometrics.events.AuthenticationAcquiredInfo; import android.hardware.biometrics.events.AuthenticationErrorInfo; import android.hardware.biometrics.events.AuthenticationFailedInfo; @@ -182,7 +181,6 @@ public class FingerprintAuthenticationClient handleLockout(authenticated); if (authenticated) { mState = STATE_STOPPED; - resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); if (reportBiometricAuthAttempts()) { mAuthenticationStateListeners.onAuthenticationSucceeded( @@ -223,7 +221,6 @@ public class FingerprintAuthenticationClient // Send the error, but do not invoke the FinishCallback yet. Since lockout is not // controlled by the HAL, the framework must stop the sensor before finishing the // client. - resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); mAuthenticationStateListeners.onAuthenticationError( new AuthenticationErrorInfo.Builder(BiometricSourceType.FINGERPRINT, @@ -275,7 +272,6 @@ public class FingerprintAuthenticationClient BiometricNotificationUtils.showBadCalibrationNotification(getContext()); } - resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); mAuthenticationStateListeners.onAuthenticationStopped(new AuthenticationStoppedInfo .Builder(BiometricSourceType.FINGERPRINT, getRequestReason()).build() @@ -284,7 +280,6 @@ public class FingerprintAuthenticationClient @Override protected void startHalOperation() { - resetIgnoreDisplayTouches(); mSensorOverlays.show(getSensorId(), getRequestReason(), this); mAuthenticationStateListeners.onAuthenticationStarted(new AuthenticationStartedInfo .Builder(BiometricSourceType.FINGERPRINT, getRequestReason()).build() @@ -331,12 +326,6 @@ public class FingerprintAuthenticationClient if (session.hasContextMethods()) { try { session.getSession().onContextChanged(ctx); - // TODO(b/317414324): Deprecate setIgnoreDisplayTouches - if (ctx.operationState != null && ctx.operationState.getTag() - == OperationState.fingerprintOperationState) { - session.getSession().setIgnoreDisplayTouches(ctx.operationState - .getFingerprintOperationState().isHardwareIgnoringTouches); - } } catch (RemoteException e) { Slog.e(TAG, "Unable to notify context changed", e); } @@ -353,7 +342,6 @@ public class FingerprintAuthenticationClient @Override protected void stopHalOperation() { - resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); mAuthenticationStateListeners.onAuthenticationStopped(new AuthenticationStoppedInfo .Builder(BiometricSourceType.FINGERPRINT, getRequestReason()).build() @@ -415,6 +403,15 @@ public class FingerprintAuthenticationClient } @Override + public void setIgnoreDisplayTouches(boolean ignoreTouches) { + try { + getFreshDaemon().getSession().setIgnoreDisplayTouches(ignoreTouches); + } catch (RemoteException e) { + Slog.e(TAG, "Remote exception", e); + } + } + + @Override public boolean isPointerDown() { return mIsPointerDown; } @@ -457,7 +454,6 @@ public class FingerprintAuthenticationClient Slog.e(TAG, "Remote exception", e); } - resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); mAuthenticationStateListeners.onAuthenticationStopped(new AuthenticationStoppedInfo .Builder(BiometricSourceType.FINGERPRINT, getRequestReason()).build() @@ -492,7 +488,6 @@ public class FingerprintAuthenticationClient Slog.e(TAG, "Remote exception", e); } - resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); mAuthenticationStateListeners.onAuthenticationStopped(new AuthenticationStoppedInfo .Builder(BiometricSourceType.FINGERPRINT, getRequestReason()).build() diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java index 36af5dba909f..fb48053913cf 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java @@ -87,7 +87,6 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession> @Override protected void stopHalOperation() { - resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); mAuthenticationStateListeners.onAuthenticationStopped( new AuthenticationStoppedInfo.Builder(BiometricSourceType.FINGERPRINT, @@ -107,7 +106,6 @@ public class FingerprintDetectClient extends AcquisitionClient<AidlSession> @Override protected void startHalOperation() { - resetIgnoreDisplayTouches(); mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD, this); mAuthenticationStateListeners.onAuthenticationStarted( diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java index 3a72d7e9a91a..993a68fd6ff8 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java @@ -150,7 +150,6 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement controller -> controller.onEnrollmentProgress(getSensorId(), remaining)); if (remaining == 0) { - resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); mAuthenticationStateListeners.onAuthenticationStopped( new AuthenticationStoppedInfo.Builder( @@ -211,7 +210,6 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement ); super.onError(errorCode, vendorCode); - resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); mAuthenticationStateListeners.onAuthenticationStopped( new AuthenticationStoppedInfo.Builder(BiometricSourceType.FINGERPRINT, @@ -227,7 +225,6 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @Override protected void startHalOperation() { - resetIgnoreDisplayTouches(); mSensorOverlays.show(getSensorId(), getRequestReasonFromFingerprintEnrollReason(mEnrollReason), this); mAuthenticationStateListeners.onAuthenticationStarted(new AuthenticationStartedInfo @@ -277,7 +274,6 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement @Override protected void stopHalOperation() { - resetIgnoreDisplayTouches(); mSensorOverlays.hide(getSensorId()); mAuthenticationStateListeners.onAuthenticationStopped(new AuthenticationStoppedInfo .Builder(BiometricSourceType.FINGERPRINT, @@ -359,5 +355,14 @@ public class FingerprintEnrollClient extends EnrollClient<AidlSession> implement } @Override + public void setIgnoreDisplayTouches(boolean ignoreTouches) { + try { + getFreshDaemon().getSession().setIgnoreDisplayTouches(ignoreTouches); + } catch (RemoteException e) { + Slog.e(TAG, "Unable to send setIgnoreDisplayTouches", e); + } + } + + @Override public void onPowerPressed() {} } diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 1bddb83b1441..12baf00c1c4a 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -790,6 +790,19 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi } @Override + public void setIgnoreDisplayTouches(long requestId, int sensorId, boolean ignoreTouches) { + mFingerprintSensors.get(sensorId).getScheduler().getCurrentClientIfMatches( + requestId, (client) -> { + if (!(client instanceof Udfps)) { + Slog.e(getTag(), + "setIgnoreDisplayTouches received during client: " + client); + return; + } + ((Udfps) client).setIgnoreDisplayTouches(ignoreTouches); + }); + } + + @Override public void onPowerPressed() { for (int i = 0; i < mFingerprintSensors.size(); i++) { final Sensor sensor = mFingerprintSensors.valueAt(i); diff --git a/services/core/java/com/android/server/display/BrightnessSetting.java b/services/core/java/com/android/server/display/BrightnessSetting.java index 651828b6b9e2..7d26004b15c5 100644 --- a/services/core/java/com/android/server/display/BrightnessSetting.java +++ b/services/core/java/com/android/server/display/BrightnessSetting.java @@ -131,6 +131,25 @@ public class BrightnessSetting { } /** + * Sets the brightness. Does not send update event to listeners. + * @param brightness The value to which the brightness is to be set. + */ + public void setBrightnessNoNotify(float brightness) { + if (Float.isNaN(brightness)) { + Slog.w(TAG, "Attempting to init invalid brightness"); + return; + } + synchronized (mSyncRoot) { + if (brightness != mBrightness) { + mPersistentDataStore.setBrightness(mLogicalDisplay.getPrimaryDisplayDeviceLocked(), + brightness, mUserSerial + ); + } + mBrightness = brightness; + } + } + + /** * @return The brightness for the default display in nits. Used when the underlying display * device has changed but we want to persist the nit value. */ diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 2d5f38e2dd8c..670d59c09e06 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -4782,6 +4782,8 @@ public final class DisplayManagerService extends SystemService { } final int[] supportedStates = mDeviceStateManager.getSupportedStateIdentifiers(); + // TODO(b/352019542): remove the log once b/345960547 is fixed. + Slog.d(TAG, "supportedStates=" + Arrays.toString(supportedStates)); DisplayInfo displayInfo; for (int state : supportedStates) { displayInfo = mLogicalDisplayMapper.getDisplayInfoForStateLocked(state, @@ -4790,6 +4792,8 @@ public final class DisplayManagerService extends SystemService { possibleInfo.add(displayInfo); } } + // TODO(b/352019542): remove the log once b/345960547 is fixed. + Slog.d(TAG, "possibleInfos=" + possibleInfo); return possibleInfo; } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index 65a729a0293e..ae59d491b8f4 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -1565,7 +1565,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // even if they range changes what it means in absolute terms. mDisplayBrightnessController.updateScreenBrightnessSetting( MathUtils.constrain(unthrottledBrightnessState, - clampedState.getMinBrightness(), clampedState.getMaxBrightness())); + clampedState.getMinBrightness(), clampedState.getMaxBrightness()), + Math.min(mBrightnessRangeController.getCurrentBrightnessMax(), + clampedState.getMaxBrightness())); } // The current brightness to use has been calculated at this point, and HbmController should @@ -2455,12 +2457,20 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call @Override public void setBrightness(float brightness) { - mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness)); + // After HBMController and NBMController migration to Clampers framework + // currentBrightnessMax should be taken from clampers controller + // TODO(b/263362199) + mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness), + mBrightnessRangeController.getCurrentBrightnessMax()); } @Override public void setBrightness(float brightness, int userSerial) { - mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness), userSerial); + // After HBMController and NBMController migration to Clampers framework + // currentBrightnessMax should be taken from clampers controller + // TODO(b/263362199) + mDisplayBrightnessController.setBrightness(clampScreenBrightness(brightness), userSerial, + mBrightnessRangeController.getCurrentBrightnessMax()); } @Override diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index e645e98c215c..4791cd1403e2 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -389,11 +389,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { // Retrieve the layout for this particular state. final Layout layout = mDeviceStateToLayoutMap.get(deviceState); if (layout == null) { + // TODO(b/352019542): remove the log once b/345960547 is fixed. + Slog.d(TAG, "Cannot get layout for given state:" + deviceState); return null; } // Retrieve the details of the given display within this layout. Layout.Display display = layout.getById(displayId); if (display == null) { + // TODO(b/352019542): remove the log once b/345960547 is fixed. + Slog.d(TAG, "Cannot get display for given layout:" + layout); return null; } // Retrieve the display info for the display that matches the display id. diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java index 4982a0b0b836..c632e77fe685 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -313,13 +313,18 @@ public final class DisplayBrightnessController { /** * Notifies the brightnessSetting to persist the supplied brightness value. */ - public void setBrightness(float brightnessValue) { + public void setBrightness(float brightnessValue, float maxBrightness) { // Update the setting, which will eventually call back into DPC to have us actually // update the display with the new value. mBrightnessSetting.setBrightness(brightnessValue); if (mDisplayId == Display.DEFAULT_DISPLAY && mPersistBrightnessNitsForDefaultDisplay) { float nits = convertToNits(brightnessValue); - if (nits >= 0) { + float currentlyStoredNits = mBrightnessSetting.getBrightnessNitsForDefaultDisplay(); + // Don't override settings if the brightness is set to max, but the currently + // stored value is greater. On multi-screen device, when switching between a + // screen with a wider brightness range and one with a narrower brightness range, + // the stored value shouldn't change. + if (nits >= 0 && !(brightnessValue == maxBrightness && currentlyStoredNits > nits)) { mBrightnessSetting.setBrightnessNitsForDefaultDisplay(nits); } } @@ -328,15 +333,15 @@ public final class DisplayBrightnessController { /** * Notifies the brightnessSetting to persist the supplied brightness value for a user. */ - public void setBrightness(float brightnessValue, int userSerial) { + public void setBrightness(float brightnessValue, int userSerial, float maxBrightness) { mBrightnessSetting.setUserSerial(userSerial); - setBrightness(brightnessValue); + setBrightness(brightnessValue, maxBrightness); } /** * Sets the current screen brightness, and notifies the BrightnessSetting about the change. */ - public void updateScreenBrightnessSetting(float brightnessValue) { + public void updateScreenBrightnessSetting(float brightnessValue, float maxBrightness) { synchronized (mLock) { if (!BrightnessUtils.isValidBrightnessValue(brightnessValue) || brightnessValue == mCurrentScreenBrightness) { @@ -345,7 +350,7 @@ public final class DisplayBrightnessController { setCurrentScreenBrightnessLocked(brightnessValue); } notifyCurrentScreenBrightness(); - setBrightness(brightnessValue); + setBrightness(brightnessValue, maxBrightness); } /** @@ -582,7 +587,7 @@ public final class DisplayBrightnessController { float brightnessForDefaultDisplay = getBrightnessFromNits( brightnessNitsForDefaultDisplay); if (BrightnessUtils.isValidBrightnessValue(brightnessForDefaultDisplay)) { - mBrightnessSetting.setBrightness(brightnessForDefaultDisplay); + mBrightnessSetting.setBrightnessNoNotify(brightnessForDefaultDisplay); currentBrightnessSetting = brightnessForDefaultDisplay; } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 183b4b520a52..0f8060bba1ab 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -3951,7 +3951,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason()); mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken, - imeVisRes.getState(), imeVisRes.getReason()); + imeVisRes.getState(), imeVisRes.getReason(), bindingController.mUserId); if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) { // If focused display changed, we should unbind current method // to make app window in previous display relayout after Ime @@ -4837,7 +4837,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. @NonNull ImeVisibilityResult result) { synchronized (ImfLock.class) { mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(), - result.getReason()); + result.getReason(), mCurrentUserId); } } @@ -5121,7 +5121,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. if (imeVisRes != null) { // Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker. mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, - null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason()); + null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason(), + mCurrentUserId); } // Eligible IME processes use new "setInteractive" protocol. mCurClient.mClient.setInteractive(mIsInteractive, mInFullscreenMode); diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java index 363a4a7be3db..91a4d6f1707f 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java @@ -1126,7 +1126,7 @@ public class ContextHubClientBroker extends IContextHubClient.Stub } } - private void sendHostEndpointConnectedEvent() { + void sendHostEndpointConnectedEvent() { HostEndpointInfo info = new HostEndpointInfo(); info.hostEndpointId = (char) mHostEndPointId; info.packageName = mPackage; diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java index b3fb147a318b..309dbed30d53 100644 --- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java +++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java @@ -242,10 +242,14 @@ public class ContextHubService extends IContextHubService.Stub { @Override public void handleServiceRestart() { - Log.i(TAG, "Starting Context Hub Service restart"); + Log.i(TAG, "Recovering from Context Hub HAL restart..."); initExistingCallbacks(); resetSettings(); - Log.i(TAG, "Finished Context Hub Service restart"); + if (Flags.reconnectHostEndpointsAfterHalRestart()) { + mClientManager.forEachClientOfHub(mContextHubId, + ContextHubClientBroker::sendHostEndpointConnectedEvent); + } + Log.i(TAG, "Finished recovering from Context Hub HAL restart"); } @Override @@ -536,6 +540,7 @@ public class ContextHubService extends IContextHubService.Stub { for (int contextHubId : mContextHubIdToInfoMap.keySet()) { try { mContextHubWrapper.registerExistingCallback(contextHubId); + Log.i(TAG, "Re-registered callback to context hub " + contextHubId); } catch (RemoteException e) { Log.e(TAG, "RemoteException while registering existing service callback for hub " + "(ID = " + contextHubId + ")", e); diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index 3673eb027096..56b93e8ded82 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -839,6 +839,13 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider { + "Disallowed route: " + route); } + + if (route.isSystemRouteType()) { + throw new SecurityException( + "Only the system is allowed to publish routes with system route types. " + + "Disallowed route: " + + route); + } } Connection connection = mConnectionRef.get(); diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 309e9450a01d..bc19b995bc14 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -189,9 +189,12 @@ public class PreferencesHelper implements RankingConfig { int USER_LOCKED_BUBBLE = 0x00000002; } + private final Object mLock = new Object(); // pkg|uid => PackagePreferences + @GuardedBy("mLock") private final ArrayMap<String, PackagePreferences> mPackagePreferences = new ArrayMap<>(); // pkg|userId => PackagePreferences + @GuardedBy("mLock") private final ArrayMap<String, PackagePreferences> mRestoredWithoutUids = new ArrayMap<>(); private final Context mContext; @@ -263,7 +266,7 @@ public class PreferencesHelper implements RankingConfig { Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW); } - synchronized (mPackagePreferences) { + synchronized (mLock) { while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { tag = parser.getName(); if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) { @@ -485,6 +488,7 @@ public class PreferencesHelper implements RankingConfig { DEFAULT_BUBBLE_PREFERENCE, mClock.millis()); } + @GuardedBy("mLock") private PackagePreferences getOrCreatePackagePreferencesLocked(String pkg, @UserIdInt int userId, int uid, int importance, int priority, int visibility, boolean showBadge, int bubblePreference, long creationTime) { @@ -616,7 +620,7 @@ public class PreferencesHelper implements RankingConfig { notifPermissions = mPermissionHelper.getNotificationPermissionValues(userId); } - synchronized (mPackagePreferences) { + synchronized (mLock) { final int N = mPackagePreferences.size(); for (int i = 0; i < N; i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); @@ -625,11 +629,10 @@ public class PreferencesHelper implements RankingConfig { } writePackageXml(r, out, notifPermissions, forBackup); } - } - if (Flags.persistIncompleteRestoreData() && !forBackup) { - synchronized (mRestoredWithoutUids) { - final int N = mRestoredWithoutUids.size(); - for (int i = 0; i < N; i++) { + + if (Flags.persistIncompleteRestoreData() && !forBackup) { + final int M = mRestoredWithoutUids.size(); + for (int i = 0; i < M; i++) { final PackagePreferences r = mRestoredWithoutUids.valueAt(i); writePackageXml(r, out, notifPermissions, false); } @@ -732,7 +735,7 @@ public class PreferencesHelper implements RankingConfig { */ public void setBubblesAllowed(String pkg, int uid, int bubblePreference) { boolean changed; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences p = getOrCreatePackagePreferencesLocked(pkg, uid); changed = p.bubblePreference != bubblePreference; p.bubblePreference = bubblePreference; @@ -752,20 +755,20 @@ public class PreferencesHelper implements RankingConfig { */ @Override public int getBubblePreference(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { return getOrCreatePackagePreferencesLocked(pkg, uid).bubblePreference; } } public int getAppLockedFields(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { return getOrCreatePackagePreferencesLocked(pkg, uid).lockedAppFields; } } @Override public boolean canShowBadge(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { return getOrCreatePackagePreferencesLocked(packageName, uid).showBadge; } } @@ -773,7 +776,7 @@ public class PreferencesHelper implements RankingConfig { @Override public void setShowBadge(String packageName, int uid, boolean showBadge) { boolean changed = false; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences pkgPrefs = getOrCreatePackagePreferencesLocked(packageName, uid); if (pkgPrefs.showBadge != showBadge) { pkgPrefs.showBadge = showBadge; @@ -786,28 +789,28 @@ public class PreferencesHelper implements RankingConfig { } public boolean isInInvalidMsgState(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); return r.hasSentInvalidMessage && !r.hasSentValidMessage; } } public boolean hasUserDemotedInvalidMsgApp(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); return isInInvalidMsgState(packageName, uid) ? r.userDemotedMsgApp : false; } } public void setInvalidMsgAppDemoted(String packageName, int uid, boolean isDemoted) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); r.userDemotedMsgApp = isDemoted; } } public boolean setInvalidMessageSent(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); boolean valueChanged = r.hasSentInvalidMessage == false; r.hasSentInvalidMessage = true; @@ -817,7 +820,7 @@ public class PreferencesHelper implements RankingConfig { } public boolean setValidMessageSent(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); boolean valueChanged = r.hasSentValidMessage == false; r.hasSentValidMessage = true; @@ -828,7 +831,7 @@ public class PreferencesHelper implements RankingConfig { @VisibleForTesting boolean hasSentInvalidMsg(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); return r.hasSentInvalidMessage; } @@ -836,7 +839,7 @@ public class PreferencesHelper implements RankingConfig { @VisibleForTesting boolean hasSentValidMsg(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); return r.hasSentValidMessage; } @@ -844,7 +847,7 @@ public class PreferencesHelper implements RankingConfig { @VisibleForTesting boolean didUserEverDemoteInvalidMsgApp(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); return r.userDemotedMsgApp; } @@ -852,7 +855,7 @@ public class PreferencesHelper implements RankingConfig { /** Sets whether this package has sent a notification with valid bubble metadata. */ public boolean setValidBubbleSent(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); boolean valueChanged = !r.hasSentValidBubble; r.hasSentValidBubble = true; @@ -861,14 +864,14 @@ public class PreferencesHelper implements RankingConfig { } boolean hasSentValidBubble(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); return r.hasSentValidBubble; } } boolean isImportanceLocked(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); return r.fixedImportance || r.defaultAppLockedImportance; } @@ -879,7 +882,7 @@ public class PreferencesHelper implements RankingConfig { if (groupId == null) { return false; } - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(packageName, uid); NotificationChannelGroup group = r.groups.get(groupId); if (group == null) { @@ -890,13 +893,13 @@ public class PreferencesHelper implements RankingConfig { } int getPackagePriority(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { return getOrCreatePackagePreferencesLocked(pkg, uid).priority; } } int getPackageVisibility(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { return getOrCreatePackagePreferencesLocked(pkg, uid).visibility; } } @@ -911,7 +914,7 @@ public class PreferencesHelper implements RankingConfig { throw new IllegalArgumentException("group.getName() can't be empty"); } boolean needsDndChange = false; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { throw new IllegalArgumentException("Invalid package"); @@ -965,7 +968,7 @@ public class PreferencesHelper implements RankingConfig { && channel.getImportance() <= IMPORTANCE_MAX, "Invalid importance level"); boolean needsPolicyFileChange = false, wasUndeleted = false, needsDndChange = false; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { throw new IllegalArgumentException("Invalid package"); @@ -1109,7 +1112,7 @@ public class PreferencesHelper implements RankingConfig { void unlockNotificationChannelImportance(String pkg, int uid, String updatedChannelId) { Objects.requireNonNull(updatedChannelId); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { throw new IllegalArgumentException("Invalid package"); @@ -1131,7 +1134,7 @@ public class PreferencesHelper implements RankingConfig { Objects.requireNonNull(updatedChannel.getId()); boolean changed = false; boolean needsDndChange = false; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { throw new IllegalArgumentException("Invalid package"); @@ -1306,7 +1309,7 @@ public class PreferencesHelper implements RankingConfig { String channelId, String conversationId, boolean returnParentIfNoConversationChannel, boolean includeDeleted) { Preconditions.checkNotNull(pkg); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { return null; @@ -1347,7 +1350,7 @@ public class PreferencesHelper implements RankingConfig { Preconditions.checkNotNull(pkg); Preconditions.checkNotNull(conversationId); List<NotificationChannel> channels = new ArrayList<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { return channels; @@ -1367,7 +1370,7 @@ public class PreferencesHelper implements RankingConfig { int callingUid, boolean fromSystemOrSystemUi) { boolean deletedChannel = false; boolean channelBypassedDnd = false; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return false; @@ -1403,7 +1406,7 @@ public class PreferencesHelper implements RankingConfig { public void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId) { Objects.requireNonNull(pkg); Objects.requireNonNull(channelId); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return; @@ -1415,7 +1418,7 @@ public class PreferencesHelper implements RankingConfig { @Override public void permanentlyDeleteNotificationChannels(String pkg, int uid) { Objects.requireNonNull(pkg); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return; @@ -1446,7 +1449,7 @@ public class PreferencesHelper implements RankingConfig { boolean fixed = mPermissionHelper.isPermissionFixed( pi.packageName, user.getUserHandle().getIdentifier()); if (fixed) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences p = getOrCreatePackagePreferencesLocked( pi.packageName, pi.applicationInfo.uid); p.fixedImportance = true; @@ -1461,7 +1464,7 @@ public class PreferencesHelper implements RankingConfig { public void updateDefaultApps(int userId, ArraySet<String> toRemove, ArraySet<Pair<String, Integer>> toAdd) { - synchronized (mPackagePreferences) { + synchronized (mLock) { for (PackagePreferences p : mPackagePreferences.values()) { if (userId == UserHandle.getUserId(p.uid)) { if (toRemove != null && toRemove.contains(p.pkg)) { @@ -1491,7 +1494,7 @@ public class PreferencesHelper implements RankingConfig { public NotificationChannelGroup getNotificationChannelGroupWithChannels(String pkg, int uid, String groupId, boolean includeDeleted) { Objects.requireNonNull(pkg); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null || groupId == null || !r.groups.containsKey(groupId)) { return null; @@ -1514,7 +1517,7 @@ public class PreferencesHelper implements RankingConfig { public NotificationChannelGroup getNotificationChannelGroup(String groupId, String pkg, int uid) { Objects.requireNonNull(pkg); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return null; @@ -1528,7 +1531,7 @@ public class PreferencesHelper implements RankingConfig { boolean includeBlocked, Set<String> activeChannelFilter) { Objects.requireNonNull(pkg); Map<String, NotificationChannelGroup> groups = new ArrayMap<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return ParceledListSlice.emptyList(); @@ -1575,7 +1578,7 @@ public class PreferencesHelper implements RankingConfig { String groupId, int callingUid, boolean fromSystemOrSystemUi) { List<NotificationChannel> deletedChannels = new ArrayList<>(); boolean groupBypassedDnd = false; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null || TextUtils.isEmpty(groupId)) { return deletedChannels; @@ -1607,7 +1610,7 @@ public class PreferencesHelper implements RankingConfig { public Collection<NotificationChannelGroup> getNotificationChannelGroups(String pkg, int uid) { List<NotificationChannelGroup> groups = new ArrayList<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return groups; @@ -1618,7 +1621,7 @@ public class PreferencesHelper implements RankingConfig { } public NotificationChannelGroup getGroupForChannel(String pkg, int uid, String channelId) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences p = getPackagePreferencesLocked(pkg, uid); if (p != null) { NotificationChannel nc = p.channels.get(channelId); @@ -1637,7 +1640,7 @@ public class PreferencesHelper implements RankingConfig { public ArrayList<ConversationChannelWrapper> getConversations(IntArray userIds, boolean onlyImportant) { - synchronized (mPackagePreferences) { + synchronized (mLock) { ArrayList<ConversationChannelWrapper> conversations = new ArrayList<>(); for (PackagePreferences p : mPackagePreferences.values()) { if (userIds.binarySearch(UserHandle.getUserId(p.uid)) >= 0) { @@ -1681,7 +1684,7 @@ public class PreferencesHelper implements RankingConfig { public ArrayList<ConversationChannelWrapper> getConversations(String pkg, int uid) { Objects.requireNonNull(pkg); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return new ArrayList<>(); @@ -1723,7 +1726,7 @@ public class PreferencesHelper implements RankingConfig { public @NonNull List<String> deleteConversations(String pkg, int uid, Set<String> conversationIds, int callingUid, boolean fromSystemOrSystemUi) { List<String> deletedChannelIds = new ArrayList<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return deletedChannelIds; @@ -1756,7 +1759,7 @@ public class PreferencesHelper implements RankingConfig { boolean includeDeleted) { Objects.requireNonNull(pkg); List<NotificationChannel> channels = new ArrayList<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return ParceledListSlice.emptyList(); @@ -1778,7 +1781,7 @@ public class PreferencesHelper implements RankingConfig { public ParceledListSlice<NotificationChannel> getNotificationChannelsBypassingDnd(String pkg, int uid) { List<NotificationChannel> channels = new ArrayList<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { final PackagePreferences r = mPackagePreferences.get( packagePreferencesKey(pkg, uid)); if (r != null) { @@ -1799,7 +1802,7 @@ public class PreferencesHelper implements RankingConfig { * upgrades. */ public boolean onlyHasDefaultChannel(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r.channels.size() == 1 && r.channels.containsKey(NotificationChannel.DEFAULT_CHANNEL_ID)) { @@ -1812,7 +1815,7 @@ public class PreferencesHelper implements RankingConfig { public int getDeletedChannelCount(String pkg, int uid) { Objects.requireNonNull(pkg); int deletedCount = 0; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return deletedCount; @@ -1831,7 +1834,7 @@ public class PreferencesHelper implements RankingConfig { public int getBlockedChannelCount(String pkg, int uid) { Objects.requireNonNull(pkg); int blockedCount = 0; - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences r = getPackagePreferencesLocked(pkg, uid); if (r == null) { return blockedCount; @@ -1874,7 +1877,7 @@ public class PreferencesHelper implements RankingConfig { ArraySet<Pair<String, Integer>> candidatePkgs = new ArraySet<>(); final IntArray currentUserIds = mUserProfiles.getCurrentProfileIds(); - synchronized (mPackagePreferences) { + synchronized (mLock) { final int numPackagePreferences = mPackagePreferences.size(); for (int i = 0; i < numPackagePreferences; i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); @@ -1943,7 +1946,7 @@ public class PreferencesHelper implements RankingConfig { * considered for sentiment adjustments (and thus never show a blocking helper). */ public void setAppImportanceLocked(String packageName, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences prefs = getOrCreatePackagePreferencesLocked(packageName, uid); if ((prefs.lockedAppFields & LockableAppFields.USER_LOCKED_IMPORTANCE) != 0) { return; @@ -1959,7 +1962,7 @@ public class PreferencesHelper implements RankingConfig { * Returns the delegate for a given package, if it's allowed by the package and the user. */ public @Nullable String getNotificationDelegate(String sourcePkg, int sourceUid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid); if (prefs == null || prefs.delegate == null) { @@ -1977,7 +1980,7 @@ public class PreferencesHelper implements RankingConfig { */ public void setNotificationDelegate(String sourcePkg, int sourceUid, String delegatePkg, int delegateUid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences prefs = getOrCreatePackagePreferencesLocked(sourcePkg, sourceUid); prefs.delegate = new Delegate(delegatePkg, delegateUid, true); } @@ -1987,7 +1990,7 @@ public class PreferencesHelper implements RankingConfig { * Used by an app to turn off its notification delegate. */ public void revokeNotificationDelegate(String sourcePkg, int sourceUid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid); if (prefs != null && prefs.delegate != null) { prefs.delegate.mEnabled = false; @@ -2001,7 +2004,7 @@ public class PreferencesHelper implements RankingConfig { */ public boolean isDelegateAllowed(String sourcePkg, int sourceUid, String potentialDelegatePkg, int potentialDelegateUid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences prefs = getPackagePreferencesLocked(sourcePkg, sourceUid); return prefs != null && prefs.isValidDelegate(potentialDelegatePkg, @@ -2047,24 +2050,25 @@ public class PreferencesHelper implements RankingConfig { pw.println("per-package config version: " + XML_VERSION); pw.println("PackagePreferences:"); - synchronized (mPackagePreferences) { + synchronized (mLock) { dumpPackagePreferencesLocked(pw, prefix, filter, mPackagePreferences, pkgPermissions); + pw.println("Restored without uid:"); + dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids, null); } - pw.println("Restored without uid:"); - dumpPackagePreferencesLocked(pw, prefix, filter, mRestoredWithoutUids, null); } public void dump(ProtoOutputStream proto, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) { - synchronized (mPackagePreferences) { + synchronized (mLock) { dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS, filter, mPackagePreferences, pkgPermissions); + dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, + filter, mRestoredWithoutUids, null); } - dumpPackagePreferencesLocked(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter, - mRestoredWithoutUids, null); } + @GuardedBy("mLock") private void dumpPackagePreferencesLocked(PrintWriter pw, String prefix, @NonNull NotificationManagerService.DumpFilter filter, ArrayMap<String, PackagePreferences> packagePreferences, @@ -2249,7 +2253,7 @@ public class PreferencesHelper implements RankingConfig { pkgsWithPermissionsToHandle = pkgPermissions.keySet(); } int pulledEvents = 0; - synchronized (mPackagePreferences) { + synchronized (mLock) { for (int i = 0; i < mPackagePreferences.size(); i++) { if (pulledEvents > NOTIFICATION_PREFERENCES_PULL_LIMIT) { break; @@ -2329,7 +2333,7 @@ public class PreferencesHelper implements RankingConfig { * {@link StatsEvent}. */ public void pullPackageChannelPreferencesStats(List<StatsEvent> events) { - synchronized (mPackagePreferences) { + synchronized (mLock) { int totalChannelsPulled = 0; for (int i = 0; i < mPackagePreferences.size(); i++) { if (totalChannelsPulled > NOTIFICATION_CHANNEL_PULL_LIMIT) { @@ -2365,7 +2369,7 @@ public class PreferencesHelper implements RankingConfig { * {@link StatsEvent}. */ public void pullPackageChannelGroupPreferencesStats(List<StatsEvent> events) { - synchronized (mPackagePreferences) { + synchronized (mLock) { int totalGroupsPulled = 0; for (int i = 0; i < mPackagePreferences.size(); i++) { if (totalGroupsPulled > NOTIFICATION_CHANNEL_GROUP_PULL_LIMIT) { @@ -2394,10 +2398,12 @@ public class PreferencesHelper implements RankingConfig { ArrayMap<Pair<Integer, String>, Pair<Boolean, Boolean>> pkgPermissions) { JSONObject ranking = new JSONObject(); JSONArray PackagePreferencess = new JSONArray(); - try { - ranking.put("noUid", mRestoredWithoutUids.size()); - } catch (JSONException e) { - // pass + synchronized (mLock) { + try { + ranking.put("noUid", mRestoredWithoutUids.size()); + } catch (JSONException e) { + // pass + } } // Track data that we've handled from the permissions-based list @@ -2406,7 +2412,7 @@ public class PreferencesHelper implements RankingConfig { pkgsWithPermissionsToHandle = pkgPermissions.keySet(); } - synchronized (mPackagePreferences) { + synchronized (mLock) { final int N = mPackagePreferences.size(); for (int i = 0; i < N; i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); @@ -2512,7 +2518,7 @@ public class PreferencesHelper implements RankingConfig { } public Map<Integer, String> getPackageBans() { - synchronized (mPackagePreferences) { + synchronized (mLock) { final int N = mPackagePreferences.size(); ArrayMap<Integer, String> packageBans = new ArrayMap<>(N); for (int i = 0; i < N; i++) { @@ -2571,7 +2577,7 @@ public class PreferencesHelper implements RankingConfig { private Map<String, Integer> getPackageChannels() { ArrayMap<String, Integer> packageChannels = new ArrayMap<>(); - synchronized (mPackagePreferences) { + synchronized (mLock) { for (int i = 0; i < mPackagePreferences.size(); i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); int channelCount = 0; @@ -2587,7 +2593,7 @@ public class PreferencesHelper implements RankingConfig { } public void onUserRemoved(int userId) { - synchronized (mPackagePreferences) { + synchronized (mLock) { int N = mPackagePreferences.size(); for (int i = N - 1; i >= 0; i--) { PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i); @@ -2599,7 +2605,7 @@ public class PreferencesHelper implements RankingConfig { } protected void onLocaleChanged(Context context, int userId) { - synchronized (mPackagePreferences) { + synchronized (mLock) { int N = mPackagePreferences.size(); for (int i = 0; i < N; i++) { PackagePreferences PackagePreferences = mPackagePreferences.valueAt(i); @@ -2628,22 +2634,24 @@ public class PreferencesHelper implements RankingConfig { for (int i = 0; i < size; i++) { final String pkg = pkgList[i]; final int uid = uidList[i]; - synchronized (mPackagePreferences) { + synchronized (mLock) { mPackagePreferences.remove(packagePreferencesKey(pkg, uid)); + mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId)); } - mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId)); updated = true; } } else { for (String pkg : pkgList) { - // Package install - final PackagePreferences r = - mRestoredWithoutUids.get(unrestoredPackageKey(pkg, changeUserId)); - if (r != null) { - try { - r.uid = mPm.getPackageUidAsUser(r.pkg, changeUserId); - mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId)); - synchronized (mPackagePreferences) { + try { + // Package install + int uid = mPm.getPackageUidAsUser(pkg, changeUserId); + PackagePermission p = null; + synchronized (mLock) { + final PackagePreferences r = + mRestoredWithoutUids.get(unrestoredPackageKey(pkg, changeUserId)); + if (r != null) { + r.uid = uid; + mRestoredWithoutUids.remove(unrestoredPackageKey(pkg, changeUserId)); mPackagePreferences.put(packagePreferencesKey(r.pkg, r.uid), r); // Try to restore any unrestored sound resources @@ -2665,32 +2673,29 @@ public class PreferencesHelper implements RankingConfig { channel.setSound(restoredUri, channel.getAudioAttributes()); } } - } - if (r.migrateToPm) { - try { - PackagePermission p = new PackagePermission( + + if (r.migrateToPm) { + p = new PackagePermission( r.pkg, UserHandle.getUserId(r.uid), r.importance != IMPORTANCE_NONE, hasUserConfiguredSettings(r)); - mPermissionHelper.setNotificationPermission(p); - } catch (Exception e) { - Slog.e(TAG, "could not migrate setting for " + r.pkg, e); } + updated = true; } - updated = true; - } catch (Exception e) { - Slog.e(TAG, "could not restore " + r.pkg, e); } + if (p != null) { + mPermissionHelper.setNotificationPermission(p); + } + } catch (Exception e) { + Slog.e(TAG, "could not restore " + pkg, e); } // Package upgrade try { - synchronized (mPackagePreferences) { - PackagePreferences fullPackagePreferences = getPackagePreferencesLocked(pkg, - mPm.getPackageUidAsUser(pkg, changeUserId)); - if (fullPackagePreferences != null) { - updated |= createDefaultChannelIfNeededLocked(fullPackagePreferences); - updated |= deleteDefaultChannelIfNeededLocked(fullPackagePreferences); - } + PackagePreferences fullPackagePreferences = getPackagePreferencesLocked(pkg, + mPm.getPackageUidAsUser(pkg, changeUserId)); + if (fullPackagePreferences != null) { + updated |= createDefaultChannelIfNeededLocked(fullPackagePreferences); + updated |= deleteDefaultChannelIfNeededLocked(fullPackagePreferences); } } catch (PackageManager.NameNotFoundException e) { } @@ -2704,7 +2709,7 @@ public class PreferencesHelper implements RankingConfig { } public void clearData(String pkg, int uid) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences p = getPackagePreferencesLocked(pkg, uid); if (p != null) { p.channels = new ArrayMap<>(); @@ -2891,7 +2896,7 @@ public class PreferencesHelper implements RankingConfig { } public void unlockAllNotificationChannels() { - synchronized (mPackagePreferences) { + synchronized (mLock) { final int numPackagePreferences = mPackagePreferences.size(); for (int i = 0; i < numPackagePreferences; i++) { final PackagePreferences r = mPackagePreferences.valueAt(i); @@ -2908,7 +2913,7 @@ public class PreferencesHelper implements RankingConfig { PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL), user.getUserHandle().getIdentifier()); for (PackageInfo pi : packages) { - synchronized (mPackagePreferences) { + synchronized (mLock) { PackagePreferences p = getOrCreatePackagePreferencesLocked( pi.packageName, pi.applicationInfo.uid); if (p.migrateToPm && p.uid != UNKNOWN_UID) { diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index f59ae168bdc3..ee0159d722b1 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -2207,11 +2207,17 @@ public class ComputerEngine implements Computer { if (PackageManagerServiceUtils.isSystemOrRoot(callingUid)) { return true; } - if (requireFullPermission) { - return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL); + boolean permissionGranted = requireFullPermission ? hasPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL) + : (hasPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS)); + if (!permissionGranted) { + if (Process.isIsolatedUid(callingUid) && isKnownIsolatedComputeApp(callingUid)) { + return checkIsolatedOwnerHasPermission(callingUid, requireFullPermission); + } } - return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) - || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS); + return permissionGranted; } /** @@ -2227,6 +2233,24 @@ public class ComputerEngine implements Computer { == PackageManager.PERMISSION_GRANTED; } + private boolean hasPermission(String permission, int uid) { + return mContext.checkPermission(permission, Process.INVALID_PID, uid) + == PackageManager.PERMISSION_GRANTED; + } + + /** + * Since isolated process cannot hold permissions, we check the permissions on the owner app + * for known isolated_compute_app cases because they belong to the same package. + */ + private boolean checkIsolatedOwnerHasPermission(int callingUid, boolean requireFullPermission) { + int ownerUid = getIsolatedOwner(callingUid); + if (requireFullPermission) { + return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, ownerUid); + } + return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, ownerUid) + || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS, ownerUid); + } + public final boolean isCallerSameApp(String packageName, int uid) { return isCallerSameApp(packageName, uid, false /* resolveIsolatedUid */); } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 1bdc58691114..009e9b862b0f 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -2504,13 +2504,13 @@ final class InstallPackageHelper { Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER); } - private void enableRestrictedSettings(String pkgName, int appId, int userId) { + private void setAccessRestrictedSettingsMode(String pkgName, int appId, int userId, int mode) { final AppOpsManager appOpsManager = mPm.mContext.getSystemService(AppOpsManager.class); final int uid = UserHandle.getUid(userId, appId); appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, uid, pkgName, - AppOpsManager.MODE_ERRORED); + mode); } /** @@ -2888,8 +2888,21 @@ final class InstallPackageHelper { mPm.notifyPackageChanged(packageName, request.getAppId()); } - if (!android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() - || !android.security.Flags.extendEcmToAllSettings()) { + // Set the OP_ACCESS_RESTRICTED_SETTINGS op, which is used by ECM (see {@link + // EnhancedConfirmationManager}) as a persistent state denoting whether an app is + // currently guarded by ECM, not guarded by ECM, or (in Android V+) that this should + // be decided later. + if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled() + && android.security.Flags.extendEcmToAllSettings()) { + final int appId = request.getAppId(); + mPm.mHandler.post(() -> { + for (int userId : firstUserIds) { + // MODE_DEFAULT means that the app's guardedness will be decided lazily + setAccessRestrictedSettingsMode(packageName, appId, userId, + AppOpsManager.MODE_DEFAULT); + } + }); + } else { // Apply restricted settings on potentially dangerous packages. Needs to happen // after appOpsManager is notified of the new package if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE @@ -2898,7 +2911,9 @@ final class InstallPackageHelper { final int appId = request.getAppId(); mPm.mHandler.post(() -> { for (int userId : firstUserIds) { - enableRestrictedSettings(packageName, appId, userId); + // MODE_ERRORED means that the app is explicitly guarded + setAccessRestrictedSettingsMode(packageName, appId, userId, + AppOpsManager.MODE_ERRORED); } }); } diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java index 563cfa4d0ecd..023f7655c7a8 100644 --- a/services/core/java/com/android/server/pm/LauncherAppsService.java +++ b/services/core/java/com/android/server/pm/LauncherAppsService.java @@ -71,6 +71,7 @@ import android.content.pm.IPackageInstallerCallback; import android.content.pm.IPackageManager; import android.content.pm.IShortcutChangeCallback; import android.content.pm.IncrementalStatesInfo; +import android.content.pm.InstallSourceInfo; import android.content.pm.LauncherActivityInfoInternal; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.ShortcutQuery; @@ -1856,9 +1857,11 @@ public class LauncherAppsService extends SystemService { private String getInstallerPackage(@NonNull String packageName, int callingUserId) { String installerPackageName = null; try { - installerPackageName = - mIPM.getInstallSourceInfo(packageName, callingUserId) - .getInstallingPackageName(); + InstallSourceInfo info = mIPM.getInstallSourceInfo(packageName, callingUserId); + if (info == null) { + return installerPackageName; + } + installerPackageName = info.getInstallingPackageName(); } catch (RemoteException re) { Slog.e(TAG, "Couldn't find installer for " + packageName, re); } diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 00e9d8d595f1..47a79a3c4051 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -1060,7 +1060,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean isInstallDpcPackagesPermissionGranted = (snapshot.checkUidPermission( android.Manifest.permission.INSTALL_DPC_PACKAGES, mInstallerUid) == PackageManager.PERMISSION_GRANTED); - final int targetPackageUid = snapshot.getPackageUid(packageName, 0, userId); + // Also query the package uid for archived packages, so that the user confirmation + // dialog can be displayed for updating archived apps. + final int targetPackageUid = snapshot.getPackageUid(packageName, + PackageManager.MATCH_ARCHIVED_PACKAGES, userId); final boolean isUpdate = targetPackageUid != -1 || isApexSession(); final InstallSourceInfo existingInstallSourceInfo = isUpdate ? snapshot.getInstallSourceInfo(packageName, userId) diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index 90d6adc4fa52..630fcb614eb3 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -185,7 +185,8 @@ final class ReconcilePackageUtils { removeAppKeySetData = true; } - if (!installRequest.isInstallSystem() && !isSystemPackage && !isApex + if ((installRequest.getScanFlags() & SCAN_BOOTING) == 0 + && !installRequest.isInstallSystem() && !isSystemPackage && !isApex && signingDetails != null && systemPackage != null && systemPackage.getSigningDetails() != null && systemPackage.getSigningDetails().checkCapability( diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index db94d0e8b0ba..695e58dad01c 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -58,6 +58,7 @@ import android.app.ActivityManager; import android.app.ActivityManagerInternal; import android.app.ActivityManagerNative; import android.app.ActivityOptions; +import android.app.AlarmManager; import android.app.BroadcastOptions; import android.app.IActivityManager; import android.app.IStopUserCallback; @@ -322,6 +323,12 @@ public class UserManagerService extends IUserManager.Stub { */ private static final long PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS = 5 * 60 * 1000; + /** + * The time duration (in milliseconds) of the window length for the auto-lock message alarm + */ + private static final long PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_ALARM_WINDOW_MS = + TimeUnit.SECONDS.toMillis(55); + // Tron counters private static final String TRON_GUEST_CREATED = "users_guest_created"; private static final String TRON_USER_CREATED = "users_user_created"; @@ -552,8 +559,15 @@ public class UserManagerService extends IUserManager.Stub { private KeyguardManager.KeyguardLockedStateListener mKeyguardLockedStateListener; - /** Token to identify and remove already scheduled private space auto-lock messages */ - private static final Object PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN = new Object(); + /** + * {@link android.app.AlarmManager.OnAlarmListener} to schedule an alarm to enable + * auto-locking private space after screen timeout + */ + private PrivateSpaceAutoLockTimer mPrivateSpaceAutoLockTimer; + + /** Tag representing the alarm manager timer for auto-locking private space */ + private static final String PRIVATE_SPACE_AUTO_LOCK_TIMER_TAG = "PrivateSpaceAutoLockTimer"; + /** Content observer to get callbacks for privte space autolock settings changes */ private final SettingsObserver mPrivateSpaceAutoLockSettingsObserver; @@ -604,22 +618,28 @@ public class UserManagerService extends IUserManager.Stub { public void onReceive(Context context, Intent intent) { if (isAutoLockForPrivateSpaceEnabled()) { if (ACTION_SCREEN_OFF.equals(intent.getAction())) { - Slog.d(LOG_TAG, "SCREEN_OFF broadcast received"); - maybeScheduleMessageToAutoLockPrivateSpace(); + maybeScheduleAlarmToAutoLockPrivateSpace(); } else if (ACTION_SCREEN_ON.equals(intent.getAction())) { Slog.d(LOG_TAG, "SCREEN_ON broadcast received, " - + "removing queued message to auto-lock private space"); - // Remove any queued messages since the device is interactive again - mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN); + + "removing pending alarms to auto-lock private space"); + // Remove any pending alarm since the device is interactive again + cancelPendingAutoLockAlarms(); } } } }; + private void cancelPendingAutoLockAlarms() { + final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); + if (alarmManager != null && mPrivateSpaceAutoLockTimer != null) { + alarmManager.cancel(mPrivateSpaceAutoLockTimer); + } + } + @VisibleForTesting - void maybeScheduleMessageToAutoLockPrivateSpace() { + void maybeScheduleAlarmToAutoLockPrivateSpace() { // No action needed if auto-lock on inactivity not selected - int privateSpaceAutoLockPreference = + final int privateSpaceAutoLockPreference = Settings.Secure.getIntForUser(mContext.getContentResolver(), Settings.Secure.PRIVATE_SPACE_AUTO_LOCK, Settings.Secure.PRIVATE_SPACE_AUTO_LOCK_AFTER_DEVICE_RESTART, @@ -632,26 +652,65 @@ public class UserManagerService extends IUserManager.Stub { } int privateProfileUserId = getPrivateProfileUserId(); if (privateProfileUserId != UserHandle.USER_NULL) { - scheduleMessageToAutoLockPrivateSpace(privateProfileUserId, - PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN, + if (isQuietModeEnabled(privateProfileUserId)) { + Slogf.d(LOG_TAG, "Not scheduling auto-lock alarm for %d, " + + "quiet mode already enabled", privateProfileUserId); + return; + } + scheduleAlarmToAutoLockPrivateSpace(privateProfileUserId, PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_TIMEOUT_MS); } } @VisibleForTesting - void scheduleMessageToAutoLockPrivateSpace(int userId, Object token, - long delayInMillis) { - Slog.i(LOG_TAG, "Scheduling auto-lock message"); - mHandler.postDelayed(() -> { + void scheduleAlarmToAutoLockPrivateSpace(int userId, long delayInMillis) { + final AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class); + if (alarmManager == null) { + Slog.e(LOG_TAG, "AlarmManager not available, cannot schedule auto-lock alarm"); + return; + } + initPrivateSpaceAutoLockTimer(userId); + final long alarmWindowStartTime = SystemClock.elapsedRealtime() + delayInMillis; + alarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, + alarmWindowStartTime, + PRIVATE_SPACE_AUTO_LOCK_INACTIVITY_ALARM_WINDOW_MS, + PRIVATE_SPACE_AUTO_LOCK_TIMER_TAG, + new HandlerExecutor(mHandler), + mPrivateSpaceAutoLockTimer); + } + + private void initPrivateSpaceAutoLockTimer(int userId) { + cancelPendingAutoLockAlarms(); + if (mPrivateSpaceAutoLockTimer == null + || mPrivateSpaceAutoLockTimer.getUserId() != userId) { + mPrivateSpaceAutoLockTimer = new PrivateSpaceAutoLockTimer(userId); + } + } + + private class PrivateSpaceAutoLockTimer implements AlarmManager.OnAlarmListener { + + private final int mUserId; + + PrivateSpaceAutoLockTimer(int userId) { + mUserId = userId; + } + + int getUserId() { + return mUserId; + } + + @Override + public void onAlarm() { final PowerManager powerManager = mContext.getSystemService(PowerManager.class); if (powerManager != null && !powerManager.isInteractive()) { - Slog.i(LOG_TAG, "Auto-locking private space with user-id " + userId); - setQuietModeEnabledAsync(userId, true, + Slog.i(LOG_TAG, "Auto-locking private space with user-id " + mUserId); + setQuietModeEnabledAsync(mUserId, true, /* target */ null, mContext.getPackageName()); } else { - Slog.i(LOG_TAG, "Device is interactive, skipping auto-lock"); + Slog.i(LOG_TAG, "Device is interactive, skipping auto-lock for profile user " + + mUserId); } - }, token, delayInMillis); + } } @RequiresPermission(Manifest.permission.SUBSCRIBE_TO_KEYGUARD_LOCKED_STATE) @@ -697,7 +756,7 @@ public class UserManagerService extends IUserManager.Stub { // Unregister device inactivity broadcasts if (mIsDeviceInactivityBroadcastReceiverRegistered) { Slog.i(LOG_TAG, "Removing device inactivity broadcast receivers"); - mHandler.removeCallbacksAndMessages(PRIVATE_SPACE_AUTO_LOCK_MESSAGE_TOKEN); + cancelPendingAutoLockAlarms(); mContext.unregisterReceiver(mDeviceInactivityBroadcastReceiver); mIsDeviceInactivityBroadcastReceiverRegistered = false; } diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java index 57ea233c0a2b..5eda6deb0eb0 100644 --- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java +++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java @@ -149,6 +149,13 @@ final class DefaultPermissionGrantPolicy { CONTACTS_PERMISSIONS.add(Manifest.permission.GET_ACCOUNTS); } + private static final Set<String> CALL_LOG_PERMISSIONS = new ArraySet<>(); + static { + CALL_LOG_PERMISSIONS.add(Manifest.permission.READ_CALL_LOG); + CALL_LOG_PERMISSIONS.add(Manifest.permission.WRITE_CALL_LOG); + } + + private static final Set<String> ALWAYS_LOCATION_PERMISSIONS = new ArraySet<>(); static { ALWAYS_LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION); @@ -753,7 +760,7 @@ final class DefaultPermissionGrantPolicy { String contactsProviderPackage = getDefaultProviderAuthorityPackage(ContactsContract.AUTHORITY, userId); grantSystemFixedPermissionsToSystemPackage(pm, contactsProviderPackage, userId, - CONTACTS_PERMISSIONS, PHONE_PERMISSIONS); + CONTACTS_PERMISSIONS, PHONE_PERMISSIONS, CALL_LOG_PERMISSIONS); grantPermissionsToSystemPackage(pm, contactsProviderPackage, userId, STORAGE_PERMISSIONS); // Device provisioning diff --git a/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java b/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java index ab22e3e3f94c..1003a8152b92 100644 --- a/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java +++ b/services/core/java/com/android/server/power/stats/GnssPowerCalculator.java @@ -126,7 +126,7 @@ public class GnssPowerCalculator extends PowerCalculator { long totalTime = 0; double totalPower = 0; for (int i = 0; i < GnssSignalQuality.NUM_GNSS_SIGNAL_QUALITY_LEVELS; i++) { - long timePerLevel = stats.getGpsSignalQualityTime(i, rawRealtimeUs, statsType); + long timePerLevel = stats.getGpsSignalQualityTime(i, rawRealtimeUs, statsType) / 1000; totalTime += timePerLevel; totalPower += mAveragePowerPerSignalQuality[i] * timePerLevel; } diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 8fc696556e29..72c7be34597e 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -17,6 +17,7 @@ package com.android.server.wallpaper; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE; import static android.Manifest.permission.READ_WALLPAPER_INTERNAL; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.WallpaperManager.COMMAND_REAPPLY; @@ -2209,7 +2210,8 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId, IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId, boolean getCropped) { - final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL); + final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL) + || hasPermission(MANAGE_EXTERNAL_STORAGE); if (!hasPrivilege) { mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true, Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b6bdc326c802..40c94dd476ef 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -158,7 +158,6 @@ import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANG import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; -import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.ActivityRecord.State.DESTROYED; @@ -1911,7 +1910,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } void updateLetterboxSurfaceIfNeeded(WindowState winHint, Transaction t) { - mLetterboxUiController.updateLetterboxSurfaceIfNeeded(winHint, t); + mLetterboxUiController.updateLetterboxSurfaceIfNeeded(winHint, t, getPendingTransaction()); } void updateLetterboxSurfaceIfNeeded(WindowState winHint) { @@ -8678,7 +8677,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } @Nullable Rect getParentAppBoundsOverride() { - return Rect.copyOrNull(mResolveConfigHint.mTmpParentAppBoundsOverride); + return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride); } /** @@ -8863,7 +8862,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } final Rect screenResolvedBounds = mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds; - final Rect parentAppBounds = mResolveConfigHint.mTmpParentAppBoundsOverride; + final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride; final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); final float screenResolvedBoundsWidth = screenResolvedBounds.width(); final float parentAppBoundsWidth = parentAppBounds.width(); @@ -9272,7 +9271,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private void resolveAspectRatioRestriction(Configuration newParentConfiguration) { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); - final Rect parentAppBounds = mResolveConfigHint.mTmpParentAppBoundsOverride; + final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride; final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); // Use tmp bounds to calculate aspect ratio so we can know whether the activity should use @@ -9313,7 +9312,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A : newParentConfiguration.windowConfiguration.getBounds(); final Rect containerAppBounds = useResolvedBounds ? new Rect(resolvedConfig.windowConfiguration.getAppBounds()) - : mResolveConfigHint.mTmpParentAppBoundsOverride; + : mResolveConfigHint.mParentAppBoundsOverride; final int requestedOrientation = getRequestedConfigurationOrientation(); final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED; diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java index 939babc4df41..c55a1003f402 100644 --- a/services/core/java/com/android/server/wm/AppTransitionController.java +++ b/services/core/java/com/android/server/wm/AppTransitionController.java @@ -256,7 +256,8 @@ public class AppTransitionController { tmpCloseApps = new ArraySet<>(mDisplayContent.mClosingApps); if (mDisplayContent.mAtmService.mBackNavigationController .removeIfContainsBackAnimationTargets(tmpOpenApps, tmpCloseApps)) { - mDisplayContent.mAtmService.mBackNavigationController.clearBackAnimations(); + mDisplayContent.mAtmService.mBackNavigationController + .clearBackAnimations(false /* cancel */); } } diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 1ce324f68569..14ae918129f7 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -252,7 +252,8 @@ class BackNavigationController { // skip if one of participant activity is translucent backType = BackNavigationInfo.TYPE_CALLBACK; } else if (prevActivities.size() > 0) { - if (!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities)) { + if ((!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities)) + && isAllActivitiesCreated(prevActivities)) { // We have another Activity in the same currentTask to go to final WindowContainer parent = currentActivity.getParent(); final boolean canCustomize = parent != null @@ -549,6 +550,17 @@ class BackNavigationController { return !prevActivities.isEmpty(); } + private static boolean isAllActivitiesCreated( + @NonNull ArrayList<ActivityRecord> prevActivities) { + for (int i = prevActivities.size() - 1; i >= 0; --i) { + final ActivityRecord check = prevActivities.get(i); + if (check.isState(ActivityRecord.State.INITIALIZING)) { + return false; + } + } + return !prevActivities.isEmpty(); + } + boolean isMonitoringTransition() { return mAnimationHandler.mComposed || mNavigationMonitor.isMonitorForRemote(); } @@ -739,7 +751,7 @@ class BackNavigationController { mObserver.sendResult(null /* result */); } if (isMonitorAnimationOrTransition()) { - clearBackAnimations(); + clearBackAnimations(true /* cancel */); } cancelPendingAnimation(); } @@ -831,24 +843,23 @@ class BackNavigationController { * Cleanup animation, this can either happen when legacy transition ready, or when the Shell * transition finish. */ - void clearBackAnimations() { - mAnimationHandler.clearBackAnimateTarget(); + void clearBackAnimations(boolean cancel) { + mAnimationHandler.clearBackAnimateTarget(cancel); mNavigationMonitor.stopMonitorTransition(); mWaitTransitionFinish = null; } /** - * Called when a transition finished. - * Handle the pending animation when the running transition finished. + * Handle the pending animation when the running transition finished, all the visibility change + * has applied so ready to start pending predictive back animation. * @param targets The final animation targets derived in transition. * @param finishedTransition The finished transition target. */ void onTransitionFinish(ArrayList<Transition.ChangeInfo> targets, @NonNull Transition finishedTransition) { if (finishedTransition == mWaitTransitionFinish) { - clearBackAnimations(); + clearBackAnimations(false /* cancel */); } - if (!mBackAnimationInProgress || mPendingAnimationBuilder == null) { return; } @@ -982,7 +993,7 @@ class BackNavigationController { mCloseAdaptor = createAdaptor(close, false, mSwitchType); if (mCloseAdaptor.mAnimationTarget == null) { Slog.w(TAG, "composeNewAnimations fail, skip"); - clearBackAnimateTarget(); + clearBackAnimateTarget(true /* cancel */); return; } @@ -1001,7 +1012,7 @@ class BackNavigationController { mOpenAnimAdaptor = new BackWindowAnimationAdaptorWrapper(true, mSwitchType, open); if (!mOpenAnimAdaptor.isValid()) { Slog.w(TAG, "compose animations fail, skip"); - clearBackAnimateTarget(); + clearBackAnimateTarget(true /* cancel */); return; } mOpenActivities = openingActivities; @@ -1013,7 +1024,7 @@ class BackNavigationController { Slog.e(TAG, "Previous animation is running " + this); return false; } - clearBackAnimateTarget(); + clearBackAnimateTarget(true /* cancel */); if (close == null || open == null || open.length == 0 || open.length > 2) { Slog.e(TAG, "reset animation with null target close: " + close + " open: " + Arrays.toString(open)); @@ -1102,7 +1113,19 @@ class BackNavigationController { return false; } - void finishPresentAnimations() { + void finishPresentAnimations(boolean cancel) { + if (mOpenActivities != null) { + for (int i = mOpenActivities.length - 1; i >= 0; --i) { + final ActivityRecord resetActivity = mOpenActivities[i]; + if (resetActivity.mDisplayContent.isFixedRotationLaunchingApp(resetActivity)) { + resetActivity.mDisplayContent + .continueUpdateOrientationForDiffOrienLaunchingApp(); + } + if (resetActivity.mLaunchTaskBehind) { + restoreLaunchBehind(resetActivity, cancel); + } + } + } if (mCloseAdaptor != null) { mCloseAdaptor.mTarget.cancelAnimation(); mCloseAdaptor = null; @@ -1111,15 +1134,6 @@ class BackNavigationController { mOpenAnimAdaptor.cleanUp(mStartingSurfaceTargetMatch); mOpenAnimAdaptor = null; } - - if (mOpenActivities != null) { - for (int i = mOpenActivities.length - 1; i >= 0; --i) { - final ActivityRecord resetActivity = mOpenActivities[i]; - if (resetActivity.mLaunchTaskBehind) { - restoreLaunchBehind(resetActivity); - } - } - } } void markStartingSurfaceMatch(SurfaceControl.Transaction reparentTransaction) { @@ -1130,10 +1144,10 @@ class BackNavigationController { mOpenAnimAdaptor.reparentWindowlessSurfaceToTarget(reparentTransaction); } - void clearBackAnimateTarget() { + void clearBackAnimateTarget(boolean cancel) { if (mComposed) { mComposed = false; - finishPresentAnimations(); + finishPresentAnimations(cancel); } mWaitTransition = false; mStartingSurfaceTargetMatch = false; @@ -1649,7 +1663,7 @@ class BackNavigationController { return; } if (!triggerBack) { - clearBackAnimateTarget(); + clearBackAnimateTarget(true /* cancel */); } else { mWaitTransition = true; } @@ -1732,25 +1746,23 @@ class BackNavigationController { true /* notifyClients */); } - private static void restoreLaunchBehind(@NonNull ActivityRecord activity) { + private static void restoreLaunchBehind(@NonNull ActivityRecord activity, boolean cancel) { if (!activity.isAttached()) { // The activity was detached from hierarchy. return; } - - if (activity.mDisplayContent.isFixedRotationLaunchingApp(activity)) { - activity.mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp(); - } - - // Restore the launch-behind state. - activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token); activity.mLaunchTaskBehind = false; - // Ignore all change - activity.mTransitionController.mSnapshotController - .mActivitySnapshotController.clearOnBackPressedActivities(); ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Setting Activity.mLauncherTaskBehind to false. Activity=%s", activity); + if (cancel) { + // Restore the launch-behind state + // TODO b/347168362 Change status directly during collecting for a transition. + activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token); + // Ignore all change + activity.mTransitionController.mSnapshotController + .mActivitySnapshotController.clearOnBackPressedActivities(); + } } void checkAnimationReady(WallpaperController wallpaperController) { @@ -1770,7 +1782,7 @@ class BackNavigationController { if (!mBackAnimationInProgress) { // gesture is already finished, do not start animation if (mPendingAnimation != null) { - clearBackAnimations(); + clearBackAnimations(true /* cancel */); mPendingAnimation = null; } return; diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java index f70d2a58e781..872b4e102565 100644 --- a/services/core/java/com/android/server/wm/KeyguardController.java +++ b/services/core/java/com/android/server/wm/KeyguardController.java @@ -16,7 +16,6 @@ package com.android.server.wm; -import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING; @@ -657,7 +656,6 @@ class KeyguardController { final boolean lastKeyguardGoingAway = mKeyguardGoingAway; final ActivityRecord lastDismissKeyguardActivity = mDismissingKeyguardActivity; - final ActivityRecord lastTurnScreenOnActivity = mTopTurnScreenOnActivity; mRequestDismissKeyguard = false; mOccluded = false; @@ -666,7 +664,6 @@ class KeyguardController { mDismissingKeyguardActivity = null; mTopTurnScreenOnActivity = null; - boolean occludedByActivity = false; final Task task = getRootTaskForControllingOccluding(display); final ActivityRecord top = task != null ? task.getTopNonFinishingActivity() : null; if (top != null) { @@ -712,7 +709,7 @@ class KeyguardController { if (mTopTurnScreenOnActivity != null && !mService.mWindowManager.mPowerManager.isInteractive() - && (mRequestDismissKeyguard || occludedByActivity)) { + && (mRequestDismissKeyguard || mOccluded)) { controller.mTaskSupervisor.wakeUp("handleTurnScreenOn"); mTopTurnScreenOnActivity.setCurrentLaunchCanTurnScreenOn(false); } diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java index 362d4efa0825..2aa7c0c0a401 100644 --- a/services/core/java/com/android/server/wm/Letterbox.java +++ b/services/core/java/com/android/server/wm/Letterbox.java @@ -20,6 +20,7 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS; import static android.view.SurfaceControl.HIDDEN; import static android.window.TaskConstants.TASK_CHILD_LAYER_LETTERBOX_BACKGROUND; +import android.annotation.NonNull; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; @@ -209,16 +210,18 @@ public class Letterbox { return false; } - public void applySurfaceChanges(SurfaceControl.Transaction t) { + /** Applies surface changes such as colour, window crop, position and input info. */ + public void applySurfaceChanges(@NonNull SurfaceControl.Transaction t, + @NonNull SurfaceControl.Transaction inputT) { if (useFullWindowSurface()) { - mFullWindowSurface.applySurfaceChanges(t); + mFullWindowSurface.applySurfaceChanges(t, inputT); for (LetterboxSurface surface : mSurfaces) { surface.remove(); } } else { for (LetterboxSurface surface : mSurfaces) { - surface.applySurfaceChanges(t); + surface.applySurfaceChanges(t, inputT); } mFullWindowSurface.remove(); @@ -418,7 +421,8 @@ public class Letterbox { return Math.max(0, mLayoutFrameGlobal.height()); } - public void applySurfaceChanges(SurfaceControl.Transaction t) { + public void applySurfaceChanges(@NonNull SurfaceControl.Transaction t, + @NonNull SurfaceControl.Transaction inputT) { if (!needsApplySurfaceChanges()) { // Nothing changed. return; @@ -446,7 +450,7 @@ public class Letterbox { } if (mSurface != null && mInputInterceptor != null) { mInputInterceptor.updateTouchableRegion(mSurfaceFrameRelative); - t.setInputWindowInfo(mSurface, mInputInterceptor.mWindowHandle); + inputT.setInputWindowInfo(mSurface, mInputInterceptor.mWindowHandle); } } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 14a0467b1d7b..e6f41ba4b496 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -785,16 +785,18 @@ final class LetterboxUiController { } void updateLetterboxSurfaceIfNeeded(WindowState winHint) { - updateLetterboxSurfaceIfNeeded(winHint, mActivityRecord.getSyncTransaction()); + updateLetterboxSurfaceIfNeeded(winHint, mActivityRecord.getSyncTransaction(), + mActivityRecord.getPendingTransaction()); } - void updateLetterboxSurfaceIfNeeded(WindowState winHint, Transaction t) { + void updateLetterboxSurfaceIfNeeded(WindowState winHint, @NonNull Transaction t, + @NonNull Transaction inputT) { if (shouldNotLayoutLetterbox(winHint)) { return; } layoutLetterboxIfNeeded(winHint); if (mLetterbox != null && mLetterbox.needsApplySurfaceChanges()) { - mLetterbox.applySurfaceChanges(t); + mLetterbox.applySurfaceChanges(t, inputT); } } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index f5ab38f72b54..4a564157c46f 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2163,6 +2163,12 @@ class RootWindowContainer extends WindowContainer<DisplayContent> // Use Task#setBoundsUnchecked to skip checking windowing mode as the windowing mode // will be updated later after this is collected in transition. rootTask.setBoundsUnchecked(taskFragment.getBounds()); + // The exit-PIP activity resumes early for seamless transition. In certain + // scenarios, this introduces unintended addition to recents. To address this, + // we mark the root task for automatic removal from recents. This ensures that + // after the pinned activity reparents to its original task, the root task is + // automatically removed from the recents list. + rootTask.autoRemoveRecents = true; // Move the last recents animation transaction from original task to the new one. if (task.mLastRecentsAnimationTransaction != null) { diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index 3b3eeb496ab7..0562cd979cb8 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -827,7 +827,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { if (changed && noSystemOverlayPermission) { if (mAlertWindowSurfaces.isEmpty()) { cancelAlertWindowNotification(); - } else if (mAlertWindowNotification == null) { + } else if (mAlertWindowNotification == null && !isSatellitePointingUiPackage()) { mAlertWindowNotification = new AlertWindowNotification(mService, mPackageName); if (mShowingAlertWindowNotificationAllowed) { mAlertWindowNotification.post(); @@ -842,6 +842,16 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { } } + // TODO b/349195999 - short term solution to not show the satellite pointing ui notification. + private boolean isSatellitePointingUiPackage() { + if (mPackageName == null || !mPackageName.equals(mService.mContext.getString( + com.android.internal.R.string.config_pointing_ui_package))) { + return false; + } + return ActivityTaskManagerService.checkPermission( + android.Manifest.permission.SATELLITE_COMMUNICATION, mPid, mUid) == PERMISSION_GRANTED; + } + void setShowingAlertWindowNotificationAllowed(boolean allowed) { mShowingAlertWindowNotificationAllowed = allowed; if (mAlertWindowNotification != null) { @@ -897,6 +907,9 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { pw.print(" mClientDead="); pw.print(mClientDead); pw.print(" mSurfaceSession="); pw.println(mSurfaceSession); pw.print(prefix); pw.print("mPackageName="); pw.println(mPackageName); + if (isSatellitePointingUiPackage()) { + pw.print(prefix); pw.println("mIsSatellitePointingUiPackage=true"); + } } @Override diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java index ab72e3c42d85..7f6499f0f4b5 100644 --- a/services/core/java/com/android/server/wm/TaskFragment.java +++ b/services/core/java/com/android/server/wm/TaskFragment.java @@ -2241,13 +2241,13 @@ class TaskFragment extends WindowContainer<WindowContainer> { static class ConfigOverrideHint { @Nullable DisplayInfo mTmpOverrideDisplayInfo; @Nullable ActivityRecord.CompatDisplayInsets mTmpCompatInsets; - @Nullable Rect mTmpParentAppBoundsOverride; + @Nullable Rect mParentAppBoundsOverride; int mTmpOverrideConfigOrientation; boolean mUseOverrideInsetsForConfig; void resolveTmpOverrides(DisplayContent dc, Configuration parentConfig, boolean isFixedRotationTransforming) { - mTmpParentAppBoundsOverride = new Rect(parentConfig.windowConfiguration.getAppBounds()); + mParentAppBoundsOverride = new Rect(parentConfig.windowConfiguration.getAppBounds()); final Insets insets; if (mUseOverrideInsetsForConfig && dc != null) { // Insets are decoupled from configuration by default from V+, use legacy @@ -2269,13 +2269,12 @@ class TaskFragment extends WindowContainer<WindowContainer> { } else { insets = Insets.NONE; } - mTmpParentAppBoundsOverride.inset(insets); + mParentAppBoundsOverride.inset(insets); } void resetTmpOverrides() { mTmpOverrideDisplayInfo = null; mTmpCompatInsets = null; - mTmpParentAppBoundsOverride = null; mTmpOverrideConfigOrientation = ORIENTATION_UNDEFINED; } } @@ -2364,7 +2363,7 @@ class TaskFragment extends WindowContainer<WindowContainer> { final Rect containingAppBounds; if (insideParentBounds) { containingAppBounds = useOverrideInsetsForConfig - ? overrideHint.mTmpParentAppBoundsOverride + ? overrideHint.mParentAppBoundsOverride : parentConfig.windowConfiguration.getAppBounds(); } else { // Restrict appBounds to display non-decor rather than parent because the diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 63ca46991c73..dcfe350b1c42 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -68,6 +68,8 @@ import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK; +import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; import static com.android.server.wm.WindowState.BLAST_TIMEOUT_DURATION; import android.annotation.IntDef; @@ -1195,7 +1197,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { throw new IllegalStateException("Can't finish a non-playing transition " + mSyncId); } mController.mFinishingTransition = this; - if (mTransientHideTasks != null && !mTransientHideTasks.isEmpty()) { // The transient hide tasks could be occluded now, e.g. returning to home. So trigger // the update to make the activities in the tasks invisible-requested, then the next @@ -1380,7 +1381,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // If the activity was just inserted to an invisible task, it will keep INITIALIZING // state. Then no need to notify the callback to avoid clearing some states // unexpectedly, e.g. launch-task-behind. - if (ar.isVisibleRequested() || !ar.isState(ActivityRecord.State.INITIALIZING)) { + // However, skip dispatch to predictive back animation target, because it only set + // launch-task-behind to make the activity become visible. + if ((ar.isVisibleRequested() || !ar.isState(ActivityRecord.State.INITIALIZING)) + && !ar.isAnimating(PARENTS, ANIMATION_TYPE_PREDICT_BACK)) { mController.dispatchLegacyAppTransitionFinished(ar); } @@ -1690,7 +1694,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // ActivityRecord#canShowWindows() may reject to show its window. The visibility also // needs to be updated for STATE_ABORT. commitVisibleActivities(transaction); - commitVisibleWallpapers(); + commitVisibleWallpapers(transaction); if (mTransactionCompletedListeners != null) { for (int i = 0; i < mTransactionCompletedListeners.size(); i++) { @@ -2121,7 +2125,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { /** * Reset waitingToshow for all wallpapers, and commit the visibility of the visible ones */ - private void commitVisibleWallpapers() { + private void commitVisibleWallpapers(SurfaceControl.Transaction t) { boolean showWallpaper = shouldWallpaperBeVisible(); for (int i = mParticipants.size() - 1; i >= 0; --i) { final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken(); @@ -2129,6 +2133,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) { wallpaper.commitVisibility(showWallpaper); } + if (showWallpaper && Flags.ensureWallpaperInTransitions() + && wallpaper.isVisibleRequested() + && getLeashSurface(wallpaper, t) != wallpaper.getSurfaceControl()) { + // If on a rotation leash, we need to explicitly show the wallpaper surface + // because shell only gets the leash and we don't allow non-transition logic + // to touch the surfaces until the transition is over. + t.show(wallpaper.getSurfaceControl()); + } } } } @@ -2541,9 +2553,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (wc.asWindowState() != null) continue; final ChangeInfo changeInfo = changes.get(wc); - - // Reject no-ops - if (!changeInfo.hasChanged()) { + // Reject no-ops, unless wallpaper + if (!changeInfo.hasChanged() + && (!Flags.ensureWallpaperInTransitions() || wc.asWallpaperToken() == null)) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " Rejecting as no-op: %s", wc); continue; @@ -2827,6 +2839,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // Use parent rotation because shell doesn't know the surface is rotated. endRotation = parent.getWindowConfiguration().getRotation(); } + } else if (isWallpaper(target) && Flags.ensureWallpaperInTransitions() + && target.getRelativeDisplayRotation() != 0 + && !target.mTransitionController.useShellTransitionsRotation()) { + // If the wallpaper is "fixed-rotated", shell is unaware of this, so use the + // "as-if-not-rotating" bounds and rotation + change.setEndAbsBounds(parent.getBounds()); + endRotation = parent.getWindowConfiguration().getRotation(); } else { change.setEndAbsBounds(bounds); } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 3e43f5a2da66..86440ac3782d 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -60,6 +60,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.internal.util.ToBooleanFunction; import com.android.server.wallpaper.WallpaperCropper.WallpaperCropUtils; +import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.ArrayList; @@ -764,10 +765,19 @@ class WallpaperController { void collectTopWallpapers(Transition transition) { if (mFindResults.hasTopShowWhenLockedWallpaper()) { - transition.collect(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper); + if (Flags.ensureWallpaperInTransitions()) { + transition.collect(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper.mToken); + } else { + transition.collect(mFindResults.mTopWallpaper.mTopShowWhenLockedWallpaper); + } + } if (mFindResults.hasTopHideWhenLockedWallpaper()) { - transition.collect(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper); + if (Flags.ensureWallpaperInTransitions()) { + transition.collect(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper.mToken); + } else { + transition.collect(mFindResults.mTopWallpaper.mTopHideWhenLockedWallpaper); + } } } diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java index 9d1551c12216..b7f8505b4a65 100644 --- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java +++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java @@ -32,6 +32,7 @@ import android.os.RemoteException; import android.util.SparseArray; import com.android.internal.protolog.common.ProtoLog; +import com.android.window.flags.Flags; import java.util.function.Consumer; @@ -80,6 +81,20 @@ class WallpaperWindowToken extends WindowToken { mDisplayContent.mWallpaperController.removeWallpaperToken(this); } + @Override + public void prepareSurfaces() { + super.prepareSurfaces(); + + if (Flags.ensureWallpaperInTransitions()) { + // Similar to Task.prepareSurfaces, outside of transitions we need to apply visibility + // changes directly. In transitions the transition player will take care of applying the + // visibility change. + if (!mTransitionController.inTransition(this)) { + getSyncTransaction().setVisibility(mSurfaceControl, isVisible()); + } + } + } + /** * Controls whether this wallpaper shows underneath the keyguard or is hidden and only * revealed once keyguard is dismissed. diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 72109d34ec8a..edab5605ab8f 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -610,10 +610,16 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Apply window transaction, syncId=%d", syncId); mService.deferWindowLayout(); mService.mTaskSupervisor.setDeferRootVisibilityUpdate(true /* deferUpdate */); - final boolean shouldDeferTransitionReady = transition != null && !t.isEmpty() - && (transition.isCollecting() || Flags.alwaysDeferTransitionWhenApplyWct()); - if (shouldDeferTransitionReady) { - transition.deferTransitionReady(); + boolean deferTransitionReady = false; + if (transition != null && !t.isEmpty()) { + if (transition.isCollecting()) { + deferTransitionReady = true; + transition.deferTransitionReady(); + } else if (Flags.alwaysDeferTransitionWhenApplyWct()) { + Slog.w(TAG, "Transition is not collecting when applyTransaction." + + " transition=" + transition + " state=" + transition.getState()); + transition = null; + } } try { final ArraySet<WindowContainer<?>> haveConfigChanges = new ArraySet<>(); @@ -767,7 +773,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub mService.mWindowManager.mWindowPlacerLocked.requestTraversal(); } } finally { - if (shouldDeferTransitionReady) { + if (deferTransitionReady) { transition.continueTransitionReady(); } mService.mTaskSupervisor.setDeferRootVisibilityUpdate(false /* deferUpdate */); diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java index 6fd7aa0e4a78..9ecd49213237 100644 --- a/services/core/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java @@ -63,6 +63,7 @@ import android.view.animation.AnimationUtils; import com.android.internal.protolog.common.LogLevel; import com.android.internal.protolog.common.ProtoLog; +import com.android.window.flags.Flags; import com.android.server.policy.WindowManagerPolicy; import java.io.PrintWriter; @@ -374,9 +375,13 @@ class WindowStateAnimator { ProtoLog.i(WM_SHOW_SURFACE_ALLOC, "SURFACE DESTROY: %s. %s", mWin, new RuntimeException().fillInStackTrace()); destroySurface(t); - // Don't hide wallpaper if we're deferring the surface destroy - // because of a surface change. - mWallpaperControllerLocked.hideWallpapers(mWin); + if (Flags.ensureWallpaperInTransitions()) { + if (mWallpaperControllerLocked.isWallpaperTarget(mWin)) { + mWin.requestUpdateWallpaperIfNeeded(); + } + } else { + mWallpaperControllerLocked.hideWallpapers(mWin); + } } catch (RuntimeException e) { Slog.w(TAG, "Exception thrown when destroying Window " + this + " surface " + mSurfaceController + " session " + mSession + ": " @@ -431,7 +436,9 @@ class WindowStateAnimator { if (!w.isOnScreen()) { hide(t, "prepareSurfaceLocked"); - mWallpaperControllerLocked.hideWallpapers(w); + if (!w.mIsWallpaper || !Flags.ensureWallpaperInTransitions()) { + mWallpaperControllerLocked.hideWallpapers(w); + } // If we are waiting for this window to handle an orientation change. If this window is // really hidden (gone for layout), there is no point in still waiting for it. diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt index bb0838db97b5..c558aae5248e 100644 --- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt @@ -1115,8 +1115,10 @@ class AppIdPermissionPolicy : SchemePolicy() { } private fun MutateStateScope.inheritImplicitPermissionStates(appId: Int, userId: Int) { + var targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT val implicitPermissions = MutableIndexedSet<String>() forEachPackageInAppId(appId) { + targetSdkVersion = targetSdkVersion.coerceAtMost(it.androidPackage!!.targetSdkVersion) implicitPermissions += it.androidPackage!!.implicitPermissions } implicitPermissions.forEachIndexed implicitPermissions@{ _, implicitPermissionName -> @@ -1153,7 +1155,10 @@ class AppIdPermissionPolicy : SchemePolicy() { newFlags = newFlags or (sourceFlags and PermissionFlags.MASK_RUNTIME) } } - if (implicitPermissionName in RETAIN_IMPLICIT_FLAGS_PERMISSIONS) { + if ( + targetSdkVersion >= Build.VERSION_CODES.M && + implicitPermissionName in NO_IMPLICIT_FLAG_PERMISSIONS + ) { newFlags = newFlags andInv PermissionFlags.IMPLICIT } else { newFlags = newFlags or PermissionFlags.IMPLICIT @@ -1782,7 +1787,7 @@ class AppIdPermissionPolicy : SchemePolicy() { private const val PLATFORM_PACKAGE_NAME = "android" // A set of permissions that we don't want to revoke when they are no longer implicit. - private val RETAIN_IMPLICIT_FLAGS_PERMISSIONS = + private val NO_IMPLICIT_FLAG_PERMISSIONS = indexedSetOf( Manifest.permission.ACCESS_MEDIA_LOCATION, Manifest.permission.ACTIVITY_RECOGNITION, diff --git a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt index 6b20ef1d5519..996daf5a5f68 100644 --- a/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt +++ b/services/permission/java/com/android/server/permission/access/util/AtomicFileExtensions.kt @@ -47,9 +47,8 @@ inline fun AtomicFile.readWithReserveCopy(block: (FileInputStream) -> Unit) { /** Write to actual file and reserve file. */ @Throws(IOException::class) inline fun AtomicFile.writeWithReserveCopy(block: (FileOutputStream) -> Unit) { - val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy") - reserveFile.delete() writeInlined(block) + val reserveFile = File(baseFile.parentFile, baseFile.name + ".reservecopy") try { FileInputStream(baseFile).use { inputStream -> FileOutputStream(reserveFile).use { outputStream -> diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index 88d3238861f5..d7936fece089 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.mock; @@ -255,7 +256,8 @@ public final class DisplayBrightnessControllerTest { @Test public void setBrightnessSetsInBrightnessSetting() { float brightnessValue = 0.3f; - mDisplayBrightnessController.setBrightness(brightnessValue); + float maxBrightnessValue = 0.65f; + mDisplayBrightnessController.setBrightness(brightnessValue, maxBrightnessValue); verify(mBrightnessSetting).setBrightness(brightnessValue); } @@ -266,20 +268,24 @@ public final class DisplayBrightnessControllerTest { // Sets the appropriate value when valid, and not equal to the current brightness float brightnessValue = 0.3f; - mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue); + float maxBrightnessValue = 0.65f; + mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue, + maxBrightnessValue); assertEquals(mDisplayBrightnessController.getCurrentBrightness(), brightnessValue, 0.0f); verify(mBrightnessChangeExecutor).execute(mOnBrightnessChangeRunnable); verify(mBrightnessSetting).setBrightness(brightnessValue); // Does nothing if the value is invalid - mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN); + clearInvocations(mBrightnessSetting); + mDisplayBrightnessController.updateScreenBrightnessSetting(Float.NaN, maxBrightnessValue); verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting); // Does nothing if the value is same as the current brightness brightnessValue = 0.2f; mDisplayBrightnessController.setAndNotifyCurrentScreenBrightness(brightnessValue); verify(mBrightnessChangeExecutor, times(2)).execute(mOnBrightnessChangeRunnable); - mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue); + mDisplayBrightnessController.updateScreenBrightnessSetting(brightnessValue, + maxBrightnessValue); verifyNoMoreInteractions(mBrightnessChangeExecutor, mBrightnessSetting); } @@ -366,7 +372,7 @@ public final class DisplayBrightnessControllerTest { when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(nits); mDisplayBrightnessController.setAutomaticBrightnessController( automaticBrightnessController); - verify(mBrightnessSetting).setBrightness(brightness); + verify(mBrightnessSetting).setBrightnessNoNotify(brightness); assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f); clearInvocations(automaticBrightnessController, mBrightnessSetting); @@ -378,7 +384,7 @@ public final class DisplayBrightnessControllerTest { when(mBrightnessSetting.getBrightness()).thenReturn(brightness); mDisplayBrightnessController.setAutomaticBrightnessController( automaticBrightnessController); - verify(mBrightnessSetting, never()).setBrightness(brightness); + verify(mBrightnessSetting, never()).setBrightnessNoNotify(brightness); assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f); clearInvocations(automaticBrightnessController, mBrightnessSetting); @@ -395,15 +401,73 @@ public final class DisplayBrightnessControllerTest { assertEquals(brightness, mDisplayBrightnessController.getCurrentBrightness(), 0.01f); verifyZeroInteractions(automaticBrightnessController); verify(mBrightnessSetting, never()).getBrightnessNitsForDefaultDisplay(); - verify(mBrightnessSetting, never()).setBrightness(brightness); + verify(mBrightnessSetting, never()).setBrightnessNoNotify(brightness); } @Test + public void testDoesNotSetBrightnessNits_settingMaxBrightnessAndStoredValueGreater() { + float brightnessValue = 0.65f; + float maxBrightness = 0.65f; + float nits = 200f; + float storedNits = 300f; + + when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(storedNits); + AutomaticBrightnessController automaticBrightnessController = mock( + AutomaticBrightnessController.class); + when(automaticBrightnessController.convertToNits(brightnessValue)).thenReturn(nits); + mDisplayBrightnessController.mAutomaticBrightnessController = automaticBrightnessController; + + mDisplayBrightnessController.setBrightness(brightnessValue, maxBrightness); + + verify(mBrightnessSetting, never()).setBrightnessNitsForDefaultDisplay(anyFloat()); + } + + @Test + public void testSetsBrightnessNits_storedValueLower() { + float brightnessValue = 0.65f; + float maxBrightness = 0.65f; + float nits = 200f; + float storedNits = 100f; + + when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(storedNits); + AutomaticBrightnessController automaticBrightnessController = mock( + AutomaticBrightnessController.class); + when(automaticBrightnessController.convertToNits(brightnessValue)).thenReturn(nits); + mDisplayBrightnessController.mAutomaticBrightnessController = automaticBrightnessController; + + mDisplayBrightnessController.setBrightness(brightnessValue, maxBrightness); + + verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits); + } + + @Test + public void testSetsBrightnessNits_lowerThanMax() { + float brightnessValue = 0.60f; + float maxBrightness = 0.65f; + float nits = 200f; + float storedNits = 300f; + + when(mBrightnessSetting.getBrightnessNitsForDefaultDisplay()).thenReturn(storedNits); + AutomaticBrightnessController automaticBrightnessController = mock( + AutomaticBrightnessController.class); + when(automaticBrightnessController.convertToNits(brightnessValue)).thenReturn(nits); + mDisplayBrightnessController.mAutomaticBrightnessController = automaticBrightnessController; + + mDisplayBrightnessController.setBrightness(brightnessValue, maxBrightness); + + verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits); + } + + + @Test public void testChangeBrightnessNitsWhenUserChanges() { float brightnessValue1 = 0.3f; float nits1 = 200f; + int userSerial1 = 1; float brightnessValue2 = 0.5f; float nits2 = 300f; + int userSerial2 = 2; + float maxBrightness = 0.65f; AutomaticBrightnessController automaticBrightnessController = mock( AutomaticBrightnessController.class); when(automaticBrightnessController.convertToNits(brightnessValue1)).thenReturn(nits1); @@ -417,13 +481,13 @@ public final class DisplayBrightnessControllerTest { verify(automaticBrightnessStrategy) .setAutomaticBrightnessController(automaticBrightnessController); - mDisplayBrightnessController.setBrightness(brightnessValue1, 1 /* user-serial */); - verify(mBrightnessSetting).setUserSerial(1); + mDisplayBrightnessController.setBrightness(brightnessValue1, userSerial1, maxBrightness); + verify(mBrightnessSetting).setUserSerial(userSerial1); verify(mBrightnessSetting).setBrightness(brightnessValue1); verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits1); - mDisplayBrightnessController.setBrightness(brightnessValue2, 2 /* user-serial */); - verify(mBrightnessSetting).setUserSerial(2); + mDisplayBrightnessController.setBrightness(brightnessValue2, userSerial2, maxBrightness); + verify(mBrightnessSetting).setUserSerial(userSerial2); verify(mBrightnessSetting).setBrightness(brightnessValue2); verify(mBrightnessSetting).setBrightnessNitsForDefaultDisplay(nits2); } diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java index 79f1574105ba..37d87c4e5d97 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java @@ -683,15 +683,14 @@ public final class UserManagerServiceTest { UserInfo privateProfileUser = mSpiedUms.createProfileForUserEvenWhenDisallowedWithThrow(PRIVATE_PROFILE_NAME, USER_TYPE_PROFILE_PRIVATE, 0, mainUser, null); - Mockito.doNothing().when(mSpiedUms).scheduleMessageToAutoLockPrivateSpace( - eq(privateProfileUser.getUserHandle().getIdentifier()), any(), - anyLong()); + Mockito.doNothing().when(mSpiedUms).scheduleAlarmToAutoLockPrivateSpace( + eq(privateProfileUser.getUserHandle().getIdentifier()), anyLong()); - mSpiedUms.maybeScheduleMessageToAutoLockPrivateSpace(); + mSpiedUms.maybeScheduleAlarmToAutoLockPrivateSpace(); - Mockito.verify(mSpiedUms).scheduleMessageToAutoLockPrivateSpace( - eq(privateProfileUser.getUserHandle().getIdentifier()), any(), anyLong()); + Mockito.verify(mSpiedUms).scheduleAlarmToAutoLockPrivateSpace( + eq(privateProfileUser.getUserHandle().getIdentifier()), anyLong()); } @Test diff --git a/services/tests/servicestests/src/com/android/server/autofill/RequestIdTest.java b/services/tests/servicestests/src/com/android/server/autofill/RequestIdTest.java index 6d56c417f789..60c3659202be 100644 --- a/services/tests/servicestests/src/com/android/server/autofill/RequestIdTest.java +++ b/services/tests/servicestests/src/com/android/server/autofill/RequestIdTest.java @@ -17,17 +17,25 @@ package com.android.server.autofill; import static com.google.common.truth.Truth.assertThat; +import android.util.Slog; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.util.ArrayList; -import java.util.List; - @RunWith(JUnit4.class) public class RequestIdTest { + private static final int TEST_DATASET_SIZE = 300; + private static final int TEST_WRAP_SIZE = 50; // Number of request ids before wrap happens + private static final String TAG = "RequestIdTest"; + List<Integer> datasetPrimaryNoWrap = new ArrayList<>(); List<Integer> datasetPrimaryWrap = new ArrayList<>(); List<Integer> datasetSecondaryNoWrap = new ArrayList<>(); @@ -35,151 +43,200 @@ public class RequestIdTest { List<Integer> datasetMixedNoWrap = new ArrayList<>(); List<Integer> datasetMixedWrap = new ArrayList<>(); - @Before - public void setup() throws Exception { - int datasetSize = 300; + List<Integer> manualWrapRequestIdList = Arrays.asList(3, 9, 15, + RequestId.MAX_SECONDARY_REQUEST_ID - 5, + RequestId.MAX_SECONDARY_REQUEST_ID - 3); + List<Integer> manualNoWrapRequestIdList =Arrays.asList(2, 6, 10, 14, 18, 22, 26, 30); + List<Integer> manualOneElementRequestIdList = Arrays.asList(1); + + @Before + public void setup() throws IllegalArgumentException { + Slog.d(TAG, "setup()"); { // Generate primary only ids that do not wrap - RequestId requestId = new RequestId(0); - for (int i = 0; i < datasetSize; i++) { + RequestId requestId = new RequestId(RequestId.MIN_PRIMARY_REQUEST_ID); + for (int i = 0; i < TEST_DATASET_SIZE; i++) { datasetPrimaryNoWrap.add(requestId.nextId(false)); } + Collections.sort(datasetPrimaryNoWrap); } { // Generate primary only ids that wrap - RequestId requestId = new RequestId(0xff00); - for (int i = 0; i < datasetSize; i++) { + RequestId requestId = new RequestId(RequestId.MAX_PRIMARY_REQUEST_ID - + TEST_WRAP_SIZE * 2); + for (int i = 0; i < TEST_DATASET_SIZE; i++) { datasetPrimaryWrap.add(requestId.nextId(false)); } + Collections.sort(datasetPrimaryWrap); } { // Generate SECONDARY only ids that do not wrap - RequestId requestId = new RequestId(0); - for (int i = 0; i < datasetSize; i++) { + RequestId requestId = new RequestId(RequestId.MIN_SECONDARY_REQUEST_ID); + for (int i = 0; i < TEST_DATASET_SIZE; i++) { datasetSecondaryNoWrap.add(requestId.nextId(true)); } + Collections.sort(datasetSecondaryNoWrap); } { // Generate SECONDARY only ids that wrap - RequestId requestId = new RequestId(0xff00); - for (int i = 0; i < datasetSize; i++) { + RequestId requestId = new RequestId(RequestId.MAX_SECONDARY_REQUEST_ID - + TEST_WRAP_SIZE * 2); + for (int i = 0; i < TEST_DATASET_SIZE; i++) { datasetSecondaryWrap.add(requestId.nextId(true)); } + Collections.sort(datasetSecondaryWrap); } { // Generate MIXED only ids that do not wrap - RequestId requestId = new RequestId(0); - for (int i = 0; i < datasetSize; i++) { + RequestId requestId = new RequestId(RequestId.MIN_REQUEST_ID); + for (int i = 0; i < TEST_DATASET_SIZE; i++) { datasetMixedNoWrap.add(requestId.nextId(i % 2 != 0)); } + Collections.sort(datasetMixedNoWrap); } { // Generate MIXED only ids that wrap - RequestId requestId = new RequestId(0xff00); - for (int i = 0; i < datasetSize; i++) { + RequestId requestId = new RequestId(RequestId.MAX_REQUEST_ID - + TEST_WRAP_SIZE); + for (int i = 0; i < TEST_DATASET_SIZE; i++) { datasetMixedWrap.add(requestId.nextId(i % 2 != 0)); } + Collections.sort(datasetMixedWrap); } + Slog.d(TAG, "finishing setup()"); } @Test public void testRequestIdLists() { + Slog.d(TAG, "testRequestIdLists()"); for (int id : datasetPrimaryNoWrap) { assertThat(RequestId.isSecondaryProvider(id)).isFalse(); - assertThat(id >= 0).isTrue(); - assertThat(id < 0xffff).isTrue(); + assertThat(id).isAtLeast(RequestId.MIN_PRIMARY_REQUEST_ID); + assertThat(id).isAtMost(RequestId.MAX_PRIMARY_REQUEST_ID); } for (int id : datasetPrimaryWrap) { assertThat(RequestId.isSecondaryProvider(id)).isFalse(); - assertThat(id >= 0).isTrue(); - assertThat(id < 0xffff).isTrue(); + assertThat(id).isAtLeast(RequestId.MIN_PRIMARY_REQUEST_ID); + assertThat(id).isAtMost(RequestId.MAX_PRIMARY_REQUEST_ID); } for (int id : datasetSecondaryNoWrap) { assertThat(RequestId.isSecondaryProvider(id)).isTrue(); - assertThat(id >= 0).isTrue(); - assertThat(id < 0xffff).isTrue(); + assertThat(id).isAtLeast(RequestId.MIN_SECONDARY_REQUEST_ID); + assertThat(id).isAtMost(RequestId.MAX_SECONDARY_REQUEST_ID); } for (int id : datasetSecondaryWrap) { assertThat(RequestId.isSecondaryProvider(id)).isTrue(); - assertThat(id >= 0).isTrue(); - assertThat(id < 0xffff).isTrue(); + assertThat(id).isAtLeast(RequestId.MIN_SECONDARY_REQUEST_ID); + assertThat(id).isAtMost(RequestId.MAX_SECONDARY_REQUEST_ID); } } @Test - public void testRequestIdGeneration() { - RequestId requestId = new RequestId(0); + public void testCreateNewRequestId() { + Slog.d(TAG, "testCreateNewRequestId()"); + for (int i = 0; i < 100000; i++) { + RequestId requestId = new RequestId(); + assertThat(requestId.getRequestId()).isAtLeast(RequestId.MIN_REQUEST_ID); + assertThat(requestId.getRequestId()).isAtMost(RequestId.MAX_START_ID); + } + } + @Test + public void testGetNextRequestId() throws IllegalArgumentException{ + Slog.d(TAG, "testGetNextRequestId()"); + RequestId requestId = new RequestId(); // Large Primary for (int i = 0; i < 100000; i++) { int y = requestId.nextId(false); assertThat(RequestId.isSecondaryProvider(y)).isFalse(); - assertThat(y >= 0).isTrue(); - assertThat(y < 0xffff).isTrue(); + assertThat(y).isAtLeast(RequestId.MIN_PRIMARY_REQUEST_ID); + assertThat(y).isAtMost(RequestId.MAX_PRIMARY_REQUEST_ID); } // Large Secondary - requestId = new RequestId(0); + requestId = new RequestId(); for (int i = 0; i < 100000; i++) { int y = requestId.nextId(true); assertThat(RequestId.isSecondaryProvider(y)).isTrue(); - assertThat(y >= 0).isTrue(); - assertThat(y < 0xffff).isTrue(); + assertThat(y).isAtLeast(RequestId.MIN_SECONDARY_REQUEST_ID); + assertThat(y).isAtMost(RequestId.MAX_SECONDARY_REQUEST_ID); } // Large Mixed - requestId = new RequestId(0); + requestId = new RequestId(); for (int i = 0; i < 50000; i++) { int y = requestId.nextId(i % 2 != 0); - assertThat(RequestId.isSecondaryProvider(y)).isEqualTo(i % 2 == 0); - assertThat(y >= 0).isTrue(); - assertThat(y < 0xffff).isTrue(); + assertThat(y).isAtLeast(RequestId.MIN_REQUEST_ID); + assertThat(y).isAtMost(RequestId.MAX_REQUEST_ID); } } @Test public void testGetLastRequestId() { - // In this test, request ids are generated FIFO, so the last entry is also the last - // request + Slog.d(TAG, "testGetLastRequestId()"); - { // Primary no wrap - int lastIdIndex = datasetPrimaryNoWrap.size() - 1; - int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetPrimaryNoWrap); - assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); + { // Primary no wrap + int lastIdIndex = datasetPrimaryNoWrap.size() - 1; + int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetPrimaryNoWrap); + assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); } - { // Primary wrap - int lastIdIndex = datasetPrimaryWrap.size() - 1; + { // Primary wrap + // The last index would be the # of request ids left after wrap + // minus 1 (index starts at 0) + int lastIdIndex = TEST_DATASET_SIZE - TEST_WRAP_SIZE - 1; int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetPrimaryWrap); - assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); + assertThat(lastComputedIdIndex).isEqualTo(lastIdIndex); } - { // Secondary no wrap + { // Secondary no wrap int lastIdIndex = datasetSecondaryNoWrap.size() - 1; int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetSecondaryNoWrap); assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); } - { // Secondary wrap - int lastIdIndex = datasetSecondaryWrap.size() - 1; + { // Secondary wrap + int lastIdIndex = TEST_DATASET_SIZE - TEST_WRAP_SIZE - 1; int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetSecondaryWrap); assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); } - { // Mixed no wrap + { // Mixed no wrap int lastIdIndex = datasetMixedNoWrap.size() - 1; int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetMixedNoWrap); assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); } - { // Mixed wrap - int lastIdIndex = datasetMixedWrap.size() - 1; + { // Mixed wrap + int lastIdIndex = TEST_DATASET_SIZE - TEST_WRAP_SIZE - 1; int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetMixedWrap); assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); } + { // Manual wrap + int lastIdIndex = 2; // [3, 9, 15, + // MAX_SECONDARY_REQUEST_ID - 5, MAX_SECONDARY_REQUEST_ID - 3] + int lastComputedIdIndex = RequestId.getLastRequestIdIndex(manualWrapRequestIdList); + assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); + } + + { // Manual no wrap + int lastIdIndex = manualNoWrapRequestIdList.size() - 1; // [2, 6, 10, 14, + // 18, 22, 26, 30] + int lastComputedIdIndex = RequestId.getLastRequestIdIndex(manualNoWrapRequestIdList); + assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); + + } + + { // Manual one element + int lastIdIndex = 0; // [1] + int lastComputedIdIndex = RequestId.getLastRequestIdIndex( + manualOneElementRequestIdList); + assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex); + + } } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java index ecd799f44552..6ec888cd2e45 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java @@ -406,8 +406,6 @@ public class FingerprintAuthenticationClientTest { mContextInjector.getValue().accept(opContext); verify(mHal).onContextChanged(same(opContext)); - verify(mHal, times(2)).setIgnoreDisplayTouches( - opContext.operationState.getFingerprintOperationState().isHardwareIgnoringTouches); client.stopHalOperation(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index d1880d2f3528..c95d19539486 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -180,6 +180,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; @SmallTest @@ -480,6 +481,34 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mPm.getPackageUidAsUser(eq(packageName), anyInt())).thenReturn(uid); } + private static void testThreadSafety(Runnable operationToTest, int nThreads, + int nRunsPerThread) throws Exception { + final CountDownLatch startLatch = new CountDownLatch(1); + final CountDownLatch doneLatch = new CountDownLatch(nThreads); + + for (int i = 0; i < nThreads; i++) { + Runnable threadRunnable = () -> { + try { + startLatch.await(); + for (int j = 0; j < nRunsPerThread; j++) { + operationToTest.run(); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + doneLatch.countDown(); + } + }; + new Thread(threadRunnable, "Test Thread #" + i).start(); + } + + // Ready set go + startLatch.countDown(); + + // Wait for all test threads to be done. + doneLatch.await(); + } + @Test public void testWriteXml_onlyBackupsTargetUser() throws Exception { // Setup package notifications. @@ -6044,6 +6073,35 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(0, actual.getChannels().stream().filter(c -> c.getId().equals("id2")).count()); } + @Test + public void testRestoredWithoutUid_threadSafety() throws Exception { + when(mPm.getPackageUidAsUser(anyString(), anyInt())).thenReturn(UNKNOWN_UID); + when(mPm.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())).thenThrow( + new PackageManager.NameNotFoundException()); + when(mClock.millis()).thenReturn(System.currentTimeMillis()); + testThreadSafety(() -> { + String id = "id"; + String xml = "<ranking version=\"1\">\n" + + "<package name=\"" + Thread.currentThread()+ "\" show_badge=\"true\">\n" + + "<channel id=\"" + id + "\" name=\"name\" importance=\"2\" " + + "show_badge=\"true\" />\n" + + "</package>\n" + + "<package name=\"" + PKG_P + "\" show_badge=\"true\">\n" + + "</package>\n" + + "</ranking>\n"; + + try { + loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // trigger a removal from the list + mXmlHelper.onPackagesChanged(true, USER_SYSTEM, new String[]{PKG_P}, + new int[]{UNKNOWN_UID}); + }, 20, 50); + } + private static NotificationChannel cloneChannel(NotificationChannel original) { Parcel parcel = Parcel.obtain(); try { diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index a39a1a8637df..3867c44696d3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -30,6 +30,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.ActivityRecord.State.STOPPED; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; @@ -158,7 +159,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { // reset drawing status to test translucent activity backNavigationInfo.onBackNavigationFinished(false); - mBackNavigationController.clearBackAnimations(); + mBackNavigationController.clearBackAnimations(true); final ActivityRecord topActivity = topTask.getTopMostActivity(); makeWindowVisibleAndDrawn(topActivity.findMainWindow()); // simulate translucent @@ -169,7 +170,8 @@ public class BackNavigationControllerTests extends WindowTestsBase { // reset drawing status to test if previous task is translucent activity backNavigationInfo.onBackNavigationFinished(false); - mBackNavigationController.clearBackAnimations(); + mBackNavigationController.clearBackAnimations(true); + makeWindowVisibleAndDrawn(topActivity.findMainWindow()); // simulate translucent recordA.setOccludesParent(false); backNavigationInfo = startBackNavigation(); @@ -180,7 +182,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { topActivity.setOccludesParent(true); recordA.setOccludesParent(true); backNavigationInfo.onBackNavigationFinished(false); - mBackNavigationController.clearBackAnimations(); + mBackNavigationController.clearBackAnimations(true); makeWindowVisibleAndDrawn(topActivity.findMainWindow()); setupKeyguardOccluded(); backNavigationInfo = startBackNavigation(); @@ -188,7 +190,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK)); backNavigationInfo.onBackNavigationFinished(false); - mBackNavigationController.clearBackAnimations(); + mBackNavigationController.clearBackAnimations(true); doReturn(true).when(recordA).canShowWhenLocked(); backNavigationInfo = startBackNavigation(); assertThat(typeToString(backNavigationInfo.getType())) @@ -245,8 +247,9 @@ public class BackNavigationControllerTests extends WindowTestsBase { assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation()); // reset drawing status + testCase.recordBack.setState(STOPPED, "stopped"); backNavigationInfo.onBackNavigationFinished(false); - mBackNavigationController.clearBackAnimations(); + mBackNavigationController.clearBackAnimations(true); makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow()); setupKeyguardOccluded(); backNavigationInfo = startBackNavigation(); @@ -255,7 +258,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { // reset drawing status, test if top activity is translucent backNavigationInfo.onBackNavigationFinished(false); - mBackNavigationController.clearBackAnimations(); + mBackNavigationController.clearBackAnimations(true); makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow()); // simulate translucent testCase.recordFront.setOccludesParent(false); @@ -266,7 +269,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { // reset drawing status, test if bottom activity is translucent backNavigationInfo.onBackNavigationFinished(false); - mBackNavigationController.clearBackAnimations(); + mBackNavigationController.clearBackAnimations(true); makeWindowVisibleAndDrawn(testCase.recordBack.findMainWindow()); // simulate translucent testCase.recordBack.setOccludesParent(false); @@ -277,7 +280,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { // reset drawing status, test canShowWhenLocked backNavigationInfo.onBackNavigationFinished(false); - mBackNavigationController.clearBackAnimations(); + mBackNavigationController.clearBackAnimations(true); doReturn(true).when(testCase.recordBack).canShowWhenLocked(); backNavigationInfo = startBackNavigation(); assertThat(typeToString(backNavigationInfo.getType())) @@ -480,7 +483,10 @@ public class BackNavigationControllerTests extends WindowTestsBase { .isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME)); backNavigationInfo.onBackNavigationFinished(false); - mBackNavigationController.clearBackAnimations(); + mBackNavigationController.clearBackAnimations(true); + + final WindowState window = topTask.getTopVisibleAppMainWindow(); + makeWindowVisibleAndDrawn(window); setupKeyguardOccluded(); backNavigationInfo = startBackNavigation(); assertThat(typeToString(backNavigationInfo.getType())) @@ -840,7 +846,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { toHomeBuilder.build(); verify(mAtm.mTaskOrganizerController, never()).addWindowlessStartingSurface( any(), any(), any(), any(), any(), any()); - animationHandler.clearBackAnimateTarget(); + animationHandler.clearBackAnimateTarget(true); openActivities.clear(); // Back to ACTIVITY and TASK have the same logic, just with different target. @@ -937,6 +943,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { testCase.recordFront = record2; testCase.windowBack = window1; testCase.windowFront = window2; + record1.setState(STOPPED, "stopped"); return testCase; } diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java index d2494ff3fc9a..fbc4c7b6bd34 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java @@ -172,14 +172,14 @@ public class LetterboxTest { @Test public void testSurfaceOrigin_applied() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).setPosition(mSurfaces.top, -1000, -2000); } @Test public void testApplySurfaceChanges_setColor() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).setColor(mSurfaces.top, new float[]{0, 0, 0}); @@ -187,7 +187,7 @@ public class LetterboxTest { assertTrue(mLetterbox.needsApplySurfaceChanges()); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).setColor(mSurfaces.top, new float[]{0, 1, 0}); } @@ -195,7 +195,7 @@ public class LetterboxTest { @Test public void testNeedsApplySurfaceChanges_wallpaperBackgroundRequested() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).setAlpha(mSurfaces.top, 1.0f); assertFalse(mLetterbox.needsApplySurfaceChanges()); @@ -204,14 +204,14 @@ public class LetterboxTest { assertTrue(mLetterbox.needsApplySurfaceChanges()); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).setAlpha(mSurfaces.fullWindowSurface, mDarkScrimAlpha); } @Test public void testNeedsApplySurfaceChanges_setParentSurface() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).reparent(mSurfaces.top, mParentSurface); assertFalse(mLetterbox.needsApplySurfaceChanges()); @@ -220,14 +220,14 @@ public class LetterboxTest { assertTrue(mLetterbox.needsApplySurfaceChanges()); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).reparent(mSurfaces.top, mParentSurface); } @Test public void testApplySurfaceChanges_cornersNotRounded_surfaceFullWindowSurfaceNotCreated() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); assertNull(mSurfaces.fullWindowSurface); } @@ -236,7 +236,7 @@ public class LetterboxTest { public void testApplySurfaceChanges_cornersRounded_surfaceFullWindowSurfaceCreated() { mAreCornersRounded = true; mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); assertNotNull(mSurfaces.fullWindowSurface); } @@ -245,7 +245,7 @@ public class LetterboxTest { public void testApplySurfaceChanges_wallpaperBackground_surfaceFullWindowSurfaceCreated() { mHasWallpaperBackground = true; mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); assertNotNull(mSurfaces.fullWindowSurface); } @@ -254,7 +254,7 @@ public class LetterboxTest { public void testNotIntersectsOrFullyContains_cornersRounded() { mAreCornersRounded = true; mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); assertTrue(mLetterbox.notIntersectsOrFullyContains(new Rect(1, 2, 9, 9))); } @@ -262,14 +262,19 @@ public class LetterboxTest { @Test public void testSurfaceOrigin_changeCausesReapply() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); clearInvocations(mTransaction); mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0)); assertTrue(mLetterbox.needsApplySurfaceChanges()); - mLetterbox.applySurfaceChanges(mTransaction); + applySurfaceChanges(); verify(mTransaction).setPosition(mSurfaces.top, 0, 0); } + private void applySurfaceChanges() { + mLetterbox.applySurfaceChanges(/* syncTransaction */ mTransaction, + /* pendingTransaction */ mTransaction); + } + class SurfaceControlMocker implements Supplier<SurfaceControl.Builder> { private SurfaceControl.Builder mLeftBuilder; public SurfaceControl left; diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index eb79118fe1c7..3078df026d8a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -392,6 +392,8 @@ public class RootWindowContainerTests extends WindowTestsBase { assertEquals(newPipTask, mDisplayContent.getDefaultTaskDisplayArea().getRootPinnedTask()); assertNotEquals(newPipTask, activity1.getTask()); assertFalse("Created PiP task must not be in recents", newPipTask.inRecents); + assertThat(newPipTask.autoRemoveRecents).isTrue(); + assertThat(activity1.getTask().autoRemoveRecents).isFalse(); } /** @@ -427,6 +429,7 @@ public class RootWindowContainerTests extends WindowTestsBase { bounds.scale(0.5f); task.setBounds(bounds); assertFalse(activity.isLetterboxedForFixedOrientationAndAspectRatio()); + assertThat(task.autoRemoveRecents).isFalse(); } /** @@ -451,6 +454,7 @@ public class RootWindowContainerTests extends WindowTestsBase { // Ensure a task has moved over. ensureTaskPlacement(task, activity); assertTrue(task.inPinnedWindowingMode()); + assertThat(task.autoRemoveRecents).isFalse(); } /** @@ -480,6 +484,8 @@ public class RootWindowContainerTests extends WindowTestsBase { ensureTaskPlacement(fullscreenTask, secondActivity); assertTrue(pinnedRootTask.inPinnedWindowingMode()); assertEquals(WINDOWING_MODE_FULLSCREEN, fullscreenTask.getWindowingMode()); + assertThat(pinnedRootTask.autoRemoveRecents).isTrue(); + assertThat(secondActivity.getTask().autoRemoveRecents).isFalse(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index e01cea3d62f8..ef0aa9ef7666 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -42,6 +42,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.policy.WindowManagerPolicy.USER_ROTATION_FREE; +import static com.android.server.wm.ActivityRecord.State.RESUMED; import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG; import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK; import static com.android.server.wm.TaskFragment.TASK_FRAGMENT_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT; @@ -222,6 +223,27 @@ public class TaskTests extends WindowTestsBase { } @Test + public void testReparentPinnedActivityBackToOriginalTask() { + final ActivityRecord activityMain = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final Task originalTask = activityMain.getTask(); + final ActivityRecord activityPip = new ActivityBuilder(mAtm).setTask(originalTask).build(); + activityPip.setState(RESUMED, "test"); + mAtm.mRootWindowContainer.moveActivityToPinnedRootTask(activityPip, + null /* launchIntoPipHostActivity */, "test"); + final Task pinnedActivityTask = activityPip.getTask(); + + // Simulate pinnedActivityTask unintentionally added to recent during top activity resume. + mAtm.getRecentTasks().getRawTasks().add(pinnedActivityTask); + + // Reparent the activity back to its original task when exiting PIP mode. + pinnedActivityTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); + + assertThat(activityPip.getTask()).isEqualTo(originalTask); + assertThat(originalTask.autoRemoveRecents).isFalse(); + assertThat(mAtm.getRecentTasks().getRawTasks()).containsExactly(originalTask); + } + + @Test public void testReparent_BetweenDisplays() { // Create first task on primary display. final Task rootTask1 = createTask(mDisplayContent); diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java index 2a359cd56d1b..6caed14bc31d 100644 --- a/telephony/java/android/telephony/satellite/SatelliteManager.java +++ b/telephony/java/android/telephony/satellite/SatelliteManager.java @@ -371,6 +371,24 @@ public final class SatelliteManager { @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) public static final int SATELLITE_RESULT_MODEM_TIMEOUT = 24; + /** + * Telephony framework needs to access the current location of the device to perform the + * request. However, location in the settings is disabled by users. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int SATELLITE_RESULT_LOCATION_DISABLED = 25; + + /** + * Telephony framework needs to access the current location of the device to perform the + * request. However, Telephony fails to fetch the current location from location service. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG) + public static final int SATELLITE_RESULT_LOCATION_NOT_AVAILABLE = 26; + /** @hide */ @IntDef(prefix = {"SATELLITE_RESULT_"}, value = { SATELLITE_RESULT_SUCCESS, @@ -397,7 +415,9 @@ public final class SatelliteManager { SATELLITE_RESULT_REQUEST_IN_PROGRESS, SATELLITE_RESULT_MODEM_BUSY, SATELLITE_RESULT_ILLEGAL_STATE, - SATELLITE_RESULT_MODEM_TIMEOUT + SATELLITE_RESULT_MODEM_TIMEOUT, + SATELLITE_RESULT_LOCATION_DISABLED, + SATELLITE_RESULT_LOCATION_NOT_AVAILABLE }) @Retention(RetentionPolicy.SOURCE) public @interface SatelliteResult {} |