Merge "Pipe nav bar transitions logic into task bar" into main
diff --git a/Ravenwood.bp b/Ravenwood.bp
index 3c8e10e..87f1124 100644
--- a/Ravenwood.bp
+++ b/Ravenwood.bp
@@ -38,8 +38,8 @@
"--out-impl-jar $(location ravenwood.jar) " +
- "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
- "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
+ "--gen-keep-all-file $(location hoststubgen_framework-minus-apex_keep_all.txt) " +
+ "--gen-input-dump-file $(location hoststubgen_framework-minus-apex_dump.txt) " +
"--in-jar $(location :framework-minus-apex-for-hoststubgen) " +
"--policy-override-file $(location :ravenwood-framework-policies) " +
@@ -54,14 +54,14 @@
"ravenwood.jar",
// Following files are created just as FYI.
- "hoststubgen_keep_all.txt",
- "hoststubgen_dump.txt",
+ "hoststubgen_framework-minus-apex_keep_all.txt",
+ "hoststubgen_framework-minus-apex_dump.txt",
"hoststubgen_framework-minus-apex.log",
"hoststubgen_framework-minus-apex_stats.csv",
"hoststubgen_framework-minus-apex_apis.csv",
],
- visibility: ["//visibility:private"],
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
}
// Extract the impl jar from "framework-minus-apex.ravenwood-base" for subsequent build rules.
@@ -79,43 +79,6 @@
],
}
-// Extract the stats file.
-genrule {
- name: "framework-minus-apex.ravenwood.stats",
- defaults: ["ravenwood-internal-only-visibility-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_stats.csv}",
- ],
- out: [
- "hoststubgen_framework-minus-apex_stats.csv",
- ],
-}
-
-genrule {
- name: "framework-minus-apex.ravenwood.apis",
- defaults: ["ravenwood-internal-only-visibility-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_apis.csv}",
- ],
- out: [
- "hoststubgen_framework-minus-apex_apis.csv",
- ],
-}
-
-genrule {
- name: "framework-minus-apex.ravenwood.keep_all",
- defaults: ["ravenwood-internal-only-visibility-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":framework-minus-apex.ravenwood-base{hoststubgen_keep_all.txt}",
- ],
- out: [
- "hoststubgen_framework-minus-apex_keep_all.txt",
- ],
-}
-
java_library {
name: "services.core-for-hoststubgen",
installable: false, // host only jar.
@@ -138,8 +101,8 @@
"--out-impl-jar $(location ravenwood.jar) " +
- "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
- "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
+ "--gen-keep-all-file $(location hoststubgen_services.core_keep_all.txt) " +
+ "--gen-input-dump-file $(location hoststubgen_services.core_dump.txt) " +
"--in-jar $(location :services.core-for-hoststubgen) " +
"--policy-override-file $(location :ravenwood-services-policies) " +
@@ -154,14 +117,14 @@
"ravenwood.jar",
// Following files are created just as FYI.
- "hoststubgen_keep_all.txt",
- "hoststubgen_dump.txt",
+ "hoststubgen_services.core_keep_all.txt",
+ "hoststubgen_services.core_dump.txt",
"hoststubgen_services.core.log",
"hoststubgen_services.core_stats.csv",
"hoststubgen_services.core_apis.csv",
],
- visibility: ["//visibility:private"],
+ defaults: ["ravenwood-internal-only-visibility-genrule"],
}
java_genrule {
@@ -176,43 +139,6 @@
],
}
-// Extract the stats file.
-genrule {
- name: "services.core.ravenwood.stats",
- defaults: ["ravenwood-internal-only-visibility-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":services.core.ravenwood-base{hoststubgen_services.core_stats.csv}",
- ],
- out: [
- "hoststubgen_services.core_stats.csv",
- ],
-}
-
-genrule {
- name: "services.core.ravenwood.apis",
- defaults: ["ravenwood-internal-only-visibility-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":services.core.ravenwood-base{hoststubgen_services.core_apis.csv}",
- ],
- out: [
- "hoststubgen_services.core_apis.csv",
- ],
-}
-
-genrule {
- name: "services.core.ravenwood.keep_all",
- defaults: ["ravenwood-internal-only-visibility-genrule"],
- cmd: "cp $(in) $(out)",
- srcs: [
- ":services.core.ravenwood-base{hoststubgen_keep_all.txt}",
- ],
- out: [
- "hoststubgen_services.core_keep_all.txt",
- ],
-}
-
java_library {
name: "services.core.ravenwood-jarjar",
defaults: ["ravenwood-internal-only-visibility-java"],
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index bb7f893..1050e1d 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -30,6 +30,7 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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,54 +1568,107 @@
}
}
- 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<>();
- 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 appendNewPath(@NonNull String path,
+ @NonNull ArraySet<String> uniquePaths, @NonNull ArrayList<String> orderedPaths) {
+ if (uniquePaths.add(path)) {
+ orderedPaths.add(path);
+ }
+ }
- 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));
- }
+ 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);
+ }
+ }
+
+ PathCollector(@Nullable ResourcesKey original) {
+ originalKey = original;
+ if (originalKey != null) {
+ appendKey(originalKey);
+ }
+ }
+
+ 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;
}
- redirectAllResourcesToNewImplLocked(updatedResourceKeys);
+ final var newKey = createNewResourceKeyIfNeeded(key, libAssets.getResourcesKey());
+ if (newKey != null) {
+ updatedResourceKeys.put(impl, newKey);
+ }
}
+ redirectAllResourcesToNewImplLocked(updatedResourceKeys);
}
private void applyNewResourceDirsLocked(@Nullable final String[] oldSourceDirs,
@@ -1718,8 +1806,9 @@
}
}
- // 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);
@@ -1835,52 +1924,35 @@
}
}
- public static class SharedLibraryAssets{
- private final String[] mAssetPaths;
+ @VisibleForTesting
+ public static class SharedLibraryAssets {
+ private final ResourcesKey mResourcesKey;
- 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]);
+ 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/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 348d4d8f..a249c39 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1647,10 +1647,13 @@
// Calling out without a lock held.
mUiAutomationConnection.executeShellCommand(command, sink, null);
- } catch (IOException ioe) {
- Log.e(LOG_TAG, "Error executing shell command!", ioe);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error executing shell command!", re);
+ } catch (IOException | RemoteException e) {
+ Log.e(LOG_TAG, "Error executing shell command!", e);
+ } catch (IllegalArgumentException | NullPointerException | SecurityException e) {
+ // An exception of these types is propagated from the server.
+ // Rethrow it to keep the old behavior. To avoid FD leak, close the source.
+ IoUtils.closeQuietly(source);
+ throw e;
} finally {
IoUtils.closeQuietly(sink);
}
@@ -1734,10 +1737,15 @@
// Calling out without a lock held.
mUiAutomationConnection.executeShellCommandWithStderr(
command, sink_read, source_write, stderr_sink_read);
- } catch (IOException ioe) {
- Log.e(LOG_TAG, "Error executing shell command!", ioe);
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error executing shell command!", re);
+ } catch (IOException | RemoteException e) {
+ Log.e(LOG_TAG, "Error executing shell command!", e);
+ } catch (IllegalArgumentException | SecurityException | NullPointerException e) {
+ // An exception of these types is propagated from the server.
+ // Rethrow it to keep the old behavior. To avoid FD leaks, close the sources.
+ IoUtils.closeQuietly(sink_write);
+ IoUtils.closeQuietly(source_read);
+ IoUtils.closeQuietly(stderr_source_read);
+ throw e;
} finally {
IoUtils.closeQuietly(sink_read);
IoUtils.closeQuietly(source_write);
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 3c4bd9e..5e21e05 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -550,8 +550,21 @@
try {
process = Runtime.getRuntime().exec(command);
- } catch (IOException exc) {
- throw new RuntimeException("Error running shell command '" + command + "'", exc);
+ } catch (IOException ex) {
+ // Make sure the passed FDs are closed.
+ IoUtils.closeQuietly(sink);
+ IoUtils.closeQuietly(source);
+ IoUtils.closeQuietly(stderrSink);
+ // No to need to wrap in RuntimeException. Only to keep the old behavior.
+ // This is just logged and not propagated to the remote caller anyway.
+ throw new RuntimeException("Error running shell command '" + command + "'", ex);
+ } catch (IllegalArgumentException | NullPointerException | SecurityException ex) {
+ // Make sure the passed FDs are closed.
+ IoUtils.closeQuietly(sink);
+ IoUtils.closeQuietly(source);
+ IoUtils.closeQuietly(stderrSink);
+ // Rethrow the exception. This will be propagated to the remote caller.
+ throw ex;
}
// Read from process and write to pipe
diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index 31157ca..0653839 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -17,7 +17,9 @@
package android.companion.virtual;
import android.app.PendingIntent;
+import android.companion.virtual.IVirtualDeviceActivityListener;
import android.companion.virtual.IVirtualDeviceIntentInterceptor;
+import android.companion.virtual.IVirtualDeviceSoundEffectListener;
import android.companion.virtual.audio.IAudioConfigChangedCallback;
import android.companion.virtual.audio.IAudioRoutingCallback;
import android.companion.virtual.sensor.VirtualSensor;
@@ -296,4 +298,15 @@
*/
@EnforcePermission("CREATE_VIRTUAL_DEVICE")
String getVirtualCameraId(in VirtualCameraConfig camera);
+
+ /**
+ * Setter for listeners that live in the client process, namely in
+ * {@link android.companion.virtual.VirtualDeviceInternal}.
+ *
+ * This is needed for virtual devices that are created by the system, as the VirtualDeviceImpl
+ * object is created before the returned VirtualDeviceInternal one.
+ */
+ @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+ void setListeners(in IVirtualDeviceActivityListener activityListener,
+ in IVirtualDeviceSoundEffectListener soundEffectListener);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index af86c97..4cbcb68 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -164,6 +164,20 @@
mSoundEffectListener);
}
+ VirtualDeviceInternal(
+ IVirtualDeviceManager service,
+ Context context,
+ IVirtualDevice virtualDevice) {
+ mService = service;
+ mContext = context.getApplicationContext();
+ mVirtualDevice = virtualDevice;
+ try {
+ mVirtualDevice.setListeners(mActivityListenerBinder, mSoundEffectListener);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
int getDeviceId() {
try {
return mVirtualDevice.getDeviceId();
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index c68014d..88c3d38 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -575,6 +575,12 @@
new VirtualDeviceInternal(service, context, associationId, params);
}
+ /** @hide */
+ public VirtualDevice(IVirtualDeviceManager service, Context context,
+ IVirtualDevice virtualDevice) {
+ mVirtualDeviceInternal = new VirtualDeviceInternal(service, context, virtualDevice);
+ }
+
/**
* Returns the unique ID of this virtual device.
*/
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index c0c1c31..899c2d6 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,6 +27,7 @@
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;
@@ -265,7 +267,7 @@
}
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*/);
}
@@ -449,7 +451,7 @@
@Deprecated
@UnsupportedAppUsage
public int addAssetPath(String path) {
- return addAssetPathInternal(List.of(path), false, false, false);
+ return addAssetPathInternal(List.of(new ApkKey(path, false, false)), false);
}
/**
@@ -459,7 +461,7 @@
@Deprecated
@UnsupportedAppUsage
public int addAssetPathAsSharedLibrary(String path) {
- return addAssetPathInternal(List.of(path), false, true, false);
+ return addAssetPathInternal(List.of(new ApkKey(path, true, false)), false);
}
/**
@@ -469,27 +471,26 @@
@Deprecated
@UnsupportedAppUsage
public int addOverlayPath(String path) {
- return addAssetPathInternal(List.of(path), true, false, false);
+ return addAssetPathInternal(List.of(new ApkKey(path, false, true)), false);
}
/**
* @hide
*/
- public void addSharedLibraryPaths(@NonNull List<String> paths) {
- addAssetPathInternal(paths, false, true, true);
+ public void addPresetApkKeys(@NonNull List<ApkKey> keys) {
+ addAssetPathInternal(keys, true);
}
- private int addAssetPathInternal(List<String> paths, boolean overlay, boolean appAsLib,
- boolean presetAssets) {
- Objects.requireNonNull(paths, "paths");
- if (paths.isEmpty()) {
+ private int addAssetPathInternal(List<ApkKey> apkKeys, boolean presetAssets) {
+ Objects.requireNonNull(apkKeys, "apkKeys");
+ if (apkKeys.isEmpty()) {
return 0;
}
synchronized (this) {
ensureOpenLocked();
- // See if we already have some of the paths loaded.
+ // 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.
@@ -498,22 +499,22 @@
assetPaths.put(mApkAssets[i].getAssetPath(), i);
}
- final ArrayList<String> newPaths = new ArrayList<>(paths.size());
+ final var newKeys = new ArrayList<ApkKey>(apkKeys.size());
int lastFoundIndex = -1;
- for (int i = 0, pathsSize = paths.size(); i < pathsSize; i++) {
- final var path = paths.get(i);
- final int index = assetPaths.getOrDefault(path, -1);
- if (index < 0) {
- newPaths.add(path);
+ 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 {
lastFoundIndex = index;
}
}
- if (newPaths.isEmpty()) {
+ if (newKeys.isEmpty()) {
return lastFoundIndex + 1;
}
- final var newAssets = loadAssets(newPaths, overlay, appAsLib);
+ final var newAssets = loadAssets(newKeys);
if (newAssets.isEmpty()) {
return 0;
}
@@ -557,28 +558,19 @@
return newAssetsArray;
}
- private static @NonNull ArrayList<ApkAssets> loadAssets(@NonNull ArrayList<String> paths,
- boolean overlay, boolean appAsLib) {
- final int pathsSize = paths.size();
+ 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 path = paths.get(i);
+ final var key = keys.get(i);
try {
- final ApkAssets assets;
- if (overlay || path.endsWith(".frro")) {
- // 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 */);
- } else {
- assets = ApkAssets.loadFromPath(path,
- appAsLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
- }
- loadedAssets.add(assets);
+ // 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, path = " + path, e);
+ Log.w(TAG, "Failed to load asset, key = " + key, e);
}
}
return loadedAssets;
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index 9f8b974..d874270 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -29,7 +29,6 @@
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,8 +47,6 @@
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
import android.os.Trace;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -72,7 +69,6 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Locale;
@@ -149,10 +145,9 @@
// 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];
@@ -206,23 +201,8 @@
public ResourcesImpl(@NonNull AssetManager assets, @Nullable DisplayMetrics metrics,
@Nullable Configuration config, @NonNull DisplayAdjustments displayAdjustments) {
mAssets = assets;
- if (Flags.registerResourcePaths()) {
- final ArraySet<String> uniquePaths = new ArraySet<>();
- final ArrayList<String> orderedPaths = new ArrayList<>();
- final ArrayMap<String, SharedLibraryAssets> sharedLibMap =
- ResourcesManager.getInstance().getSharedLibAssetsMap();
- final int size = sharedLibMap.size();
- for (int i = 0; i < size; i++) {
- final var paths = sharedLibMap.valueAt(i).getAllAssetPaths();
- for (int j = 0; j < paths.length; j++) {
- if (uniquePaths.add(paths[j])) {
- orderedPaths.add(paths[j]);
- }
- }
- }
- assets.addSharedLibraryPaths(orderedPaths);
- mSharedLibCount = size;
- }
+ mAppliedSharedLibsHash =
+ ResourcesManager.getInstance().updateResourceImplWithRegisteredLibs(this);
mMetrics.setToDefaults();
mDisplayAdjustments = displayAdjustments;
mConfiguration.setToDefaults();
@@ -1625,7 +1605,7 @@
}
}
- public int getSharedLibCount() {
- return mSharedLibCount;
+ public int getAppliedSharedLibsHash() {
+ return mAppliedSharedLibsHash;
}
}
diff --git a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
index b8f2c00..3be911abe7 100644
--- a/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
+++ b/core/java/android/hardware/input/VirtualRotaryEncoderScrollEvent.java
@@ -69,8 +69,13 @@
}
/**
- * Returns the scroll amount, normalized from -1.0 to 1.0, inclusive. Positive values
- * indicate scrolling forward (e.g. down in a vertical list); negative values, backward.
+ * Returns the scroll amount, normalized from -1.0 to 1.0, inclusive.
+ * <p>
+ * Positive values indicate scrolling forward (e.g. down in a vertical list); negative values,
+ * backward.
+ * <p>
+ * Values of 1.0 or -1.0 represent the maximum supported scroll.
+ * </p>
*/
public @FloatRange(from = -1.0f, to = 1.0f) float getScrollAmount() {
return mScrollAmount;
@@ -91,7 +96,7 @@
*/
public static final class Builder {
- private float mScrollAmount;
+ @FloatRange(from = -1.0f, to = 1.0f) private float mScrollAmount = 0.0f;
private long mEventTimeNanos = 0L;
/**
@@ -102,9 +107,13 @@
}
/**
- * Sets the scroll amount, normalized from -1.0 to 1.0, inclusive. Positive values
- * indicate scrolling forward (e.g. down in a vertical list); negative values, backward.
- *
+ * Sets the scroll amount, normalized from -1.0 to 1.0, inclusive.
+ * <p>
+ * Positive values indicate scrolling forward (e.g. down in a vertical list); negative
+ * values, backward.
+ * <p>
+ * Values of 1.0 or -1.0 represent the maximum supported scroll.
+ * </p>
* @return this builder, to allow for chaining of calls
*/
public @NonNull Builder setScrollAmount(
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a0cf203..c0bd535 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -22,6 +22,8 @@
import static android.graphics.HardwareRenderer.SYNC_LOST_SURFACE_REWARD_IF_FOUND;
import static android.os.IInputConstants.INVALID_INPUT_EVENT_ID;
import static android.os.Trace.TRACE_TAG_VIEW;
+import static android.util.SequenceUtils.getInitSeq;
+import static android.util.SequenceUtils.isIncomingSeqNewer;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.DragEvent.ACTION_DRAG_LOCATION;
@@ -128,6 +130,7 @@
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.insetsControlSeq;
import static com.android.window.flags.Flags.setScPropertiesInClient;
import android.Manifest;
@@ -892,6 +895,12 @@
/** Non-{@code null} if {@link #mActivityConfigCallback} is not {@code null}. */
@Nullable
private ActivityWindowInfo mLastReportedActivityWindowInfo;
+ @Nullable
+ private final ClientWindowFrames mLastReportedFrames = insetsControlSeq()
+ ? new ClientWindowFrames()
+ : null;
+ private int mLastReportedInsetsStateSeq = getInitSeq();
+ private int mLastReportedActiveControlsSeq = getInitSeq();
boolean mScrollMayChange;
@SoftInputModeFlags
@@ -1596,8 +1605,6 @@
attachedFrame = null;
}
if (mTranslator != null) {
- mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
- mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls.get());
mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
mTmpFrames.attachedFrame = attachedFrame;
@@ -1620,8 +1627,7 @@
mAttachInfo.mAlwaysConsumeSystemBars =
(res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0;
mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
- mInsetsController.onStateChanged(mTempInsets);
- mInsetsController.onControlsChanged(mTempControls.get());
+ handleInsetsControlChanged(mTempInsets, mTempControls);
final InsetsState state = mInsetsController.getState();
final Rect displayCutoutSafe = mTempRect;
state.getDisplayCutoutSafe(displayCutoutSafe);
@@ -2219,17 +2225,18 @@
return;
}
+ onClientWindowFramesChanged(frames);
+
CompatibilityInfo.applyOverrideScaleIfNeeded(mergedConfiguration);
final Rect frame = frames.frame;
final Rect displayFrame = frames.displayFrame;
final Rect attachedFrame = frames.attachedFrame;
if (mTranslator != null) {
- mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
mTranslator.translateRectInScreenToAppWindow(frame);
mTranslator.translateRectInScreenToAppWindow(displayFrame);
mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
- mInsetsController.onStateChanged(insetsState);
+ onInsetsStateChanged(insetsState);
final float compatScale = frames.compatScale;
final boolean frameChanged = !mWinFrame.equals(frame);
final boolean shouldReportActivityWindowInfoChanged =
@@ -2294,26 +2301,69 @@
}
/** Handles messages {@link #MSG_INSETS_CONTROL_CHANGED}. */
- private void handleInsetsControlChanged(@NonNull InsetsState insetsState,
+ @VisibleForTesting
+ public void handleInsetsControlChanged(@NonNull InsetsState insetsState,
@NonNull InsetsSourceControl.Array activeControls) {
- final InsetsSourceControl[] controls = activeControls.get();
-
- if (mTranslator != null) {
- mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
- mTranslator.translateSourceControlsInScreenToAppWindow(controls);
- }
-
// Deliver state change before control change, such that:
// a) When gaining control, controller can compare with server state to evaluate
// whether it needs to run animation.
// b) When loosing control, controller can restore server state by taking last
// dispatched state as truth.
- mInsetsController.onStateChanged(insetsState);
- if (mAdded) {
- mInsetsController.onControlsChanged(controls);
- } else {
- activeControls.release();
+ onInsetsStateChanged(insetsState);
+ onActiveControlsChanged(activeControls);
+ }
+
+ private void onClientWindowFramesChanged(@NonNull ClientWindowFrames inOutFrames) {
+ if (mLastReportedFrames == null) {
+ return;
}
+ if (isIncomingSeqNewer(mLastReportedFrames.seq, inOutFrames.seq)) {
+ // Keep track of the latest.
+ mLastReportedFrames.setTo(inOutFrames);
+ } else {
+ // If the last reported frames is newer, use the last reported instead.
+ inOutFrames.setTo(mLastReportedFrames);
+ }
+ }
+
+ private void onInsetsStateChanged(@NonNull InsetsState insetsState) {
+ if (insetsControlSeq()) {
+ if (isIncomingSeqNewer(mLastReportedInsetsStateSeq, insetsState.getSeq())) {
+ mLastReportedInsetsStateSeq = insetsState.getSeq();
+ } else {
+ // The last reported InsetsState is newer. Skip.
+ return;
+ }
+ }
+
+ if (mTranslator != null) {
+ mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
+ }
+ mInsetsController.onStateChanged(insetsState);
+ }
+
+ private void onActiveControlsChanged(@NonNull InsetsSourceControl.Array activeControls) {
+ if (!mAdded) {
+ // Do not update the last report if window is not added yet.
+ activeControls.release();
+ return;
+ }
+
+ if (insetsControlSeq()) {
+ if (isIncomingSeqNewer(mLastReportedActiveControlsSeq, activeControls.getSeq())) {
+ mLastReportedActiveControlsSeq = activeControls.getSeq();
+ } else {
+ // The last reported controls is newer. Skip.
+ activeControls.release();
+ return;
+ }
+ }
+
+ final InsetsSourceControl[] controls = activeControls.get();
+ if (mTranslator != null) {
+ mTranslator.translateSourceControlsInScreenToAppWindow(controls);
+ }
+ mInsetsController.onControlsChanged(controls);
}
private final DisplayListener mDisplayListener = new DisplayListener() {
@@ -9268,6 +9318,8 @@
mRelayoutSeq, mLastSyncSeqId, mRelayoutResult);
mRelayoutRequested = true;
+ onClientWindowFramesChanged(mTmpFrames);
+
if (activityWindowInfoFlag() && mPendingActivityWindowInfo != null) {
final ActivityWindowInfo outInfo = mRelayoutResult.activityWindowInfo;
if (outInfo != null) {
@@ -9284,13 +9336,10 @@
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
- mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
- mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls.get());
}
mInvCompatScale = 1f / mTmpFrames.compatScale;
CompatibilityInfo.applyOverrideScaleIfNeeded(mPendingMergedConfiguration);
- mInsetsController.onStateChanged(mTempInsets);
- mInsetsController.onControlsChanged(mTempControls.get());
+ handleInsetsControlChanged(mTempInsets, mTempControls);
mPendingAlwaysConsumeSystemBars =
(relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index 618f622..ab04851 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -160,8 +160,20 @@
*/
public static final int CUJ_DESKTOP_MODE_RESIZE_WINDOW = 106;
+ /** Track entering desktop mode interaction. */
+ public static final int CUJ_DESKTOP_MODE_ENTER_MODE = 107;
+
+ /** Track exiting desktop mode interaction. */
+ public static final int CUJ_DESKTOP_MODE_EXIT_MODE = 108;
+
+ /** Track minimize window interaction in desktop mode. */
+ public static final int CUJ_DESKTOP_MODE_MINIMIZE_WINDOW = 109;
+
+ /** Track window drag interaction in desktop mode. */
+ public static final int CUJ_DESKTOP_MODE_DRAG_WINDOW = 110;
+
// When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
- @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_RESIZE_WINDOW;
+ @VisibleForTesting static final int LAST_CUJ = CUJ_DESKTOP_MODE_DRAG_WINDOW;
/** @hide */
@IntDef({
@@ -259,7 +271,11 @@
CUJ_LAUNCHER_PRIVATE_SPACE_UNLOCK,
CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW,
CUJ_FOLD_ANIM,
- CUJ_DESKTOP_MODE_RESIZE_WINDOW
+ CUJ_DESKTOP_MODE_RESIZE_WINDOW,
+ CUJ_DESKTOP_MODE_ENTER_MODE,
+ CUJ_DESKTOP_MODE_EXIT_MODE,
+ CUJ_DESKTOP_MODE_MINIMIZE_WINDOW,
+ CUJ_DESKTOP_MODE_DRAG_WINDOW
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {}
@@ -368,6 +384,10 @@
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MAXIMIZE_WINDOW;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_FOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__FOLD_ANIM;
CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_RESIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_RESIZE_WINDOW;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_ENTER_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_ENTER_MODE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_EXIT_MODE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_EXIT_MODE;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_MINIMIZE_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_MINIMIZE_WINDOW;
+ CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_DESKTOP_MODE_DRAG_WINDOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__DESKTOP_MODE_DRAG_WINDOW;
}
private Cuj() {
@@ -576,6 +596,14 @@
return "FOLD_ANIM";
case CUJ_DESKTOP_MODE_RESIZE_WINDOW:
return "DESKTOP_MODE_RESIZE_WINDOW";
+ case CUJ_DESKTOP_MODE_ENTER_MODE:
+ return "DESKTOP_MODE_ENTER_MODE";
+ case CUJ_DESKTOP_MODE_EXIT_MODE:
+ return "DESKTOP_MODE_EXIT_MODE";
+ case CUJ_DESKTOP_MODE_MINIMIZE_WINDOW:
+ return "DESKTOP_MODE_MINIMIZE_WINDOW";
+ case CUJ_DESKTOP_MODE_DRAG_WINDOW:
+ return "DESKTOP_MODE_DRAG_WINDOW";
}
return "UNKNOWN";
}
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 66b2a9c..238e6f5 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -75,10 +75,11 @@
/** @hide */
public class TransitionAnimation {
public static final int WALLPAPER_TRANSITION_NONE = 0;
- public static final int WALLPAPER_TRANSITION_OPEN = 1;
- public static final int WALLPAPER_TRANSITION_CLOSE = 2;
- public static final int WALLPAPER_TRANSITION_INTRA_OPEN = 3;
- public static final int WALLPAPER_TRANSITION_INTRA_CLOSE = 4;
+ public static final int WALLPAPER_TRANSITION_CHANGE = 1;
+ public static final int WALLPAPER_TRANSITION_OPEN = 2;
+ public static final int WALLPAPER_TRANSITION_CLOSE = 3;
+ public static final int WALLPAPER_TRANSITION_INTRA_OPEN = 4;
+ public static final int WALLPAPER_TRANSITION_INTRA_CLOSE = 5;
// These are the possible states for the enter/exit activities during a thumbnail transition
private static final int THUMBNAIL_TRANSITION_ENTER_SCALE_UP = 0;
diff --git a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
index 00262be..5c2a167 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/CoreDocument.java
@@ -15,6 +15,7 @@
*/
package com.android.internal.widget.remotecompose.core;
+import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
import com.android.internal.widget.remotecompose.core.operations.Theme;
@@ -308,9 +309,10 @@
/**
* Returns true if x,y coordinate is within bounds
+ *
* @param x x-coordinate
* @param y y-coordinate
- * @return x,y coordinate is within bounds
+ * @return x, y coordinate is within bounds
*/
public boolean contains(float x, float y) {
return x >= mLeft && x < mRight
@@ -483,6 +485,37 @@
return builder.toString();
}
+ /**
+ * Gets the names of all named colors.
+ *
+ * @return array of named colors or null
+ */
+ public String[] getNamedColors() {
+ int count = 0;
+ for (Operation op : mOperations) {
+ if (op instanceof NamedVariable) {
+ NamedVariable n = (NamedVariable) op;
+ if (n.mVarType == NamedVariable.COLOR_TYPE) {
+ count++;
+ }
+ }
+ }
+ if (count == 0) {
+ return null;
+ }
+ String[] ret = new String[count];
+ int i = 0;
+ for (Operation op : mOperations) {
+ if (op instanceof NamedVariable) {
+ NamedVariable n = (NamedVariable) op;
+ if (n.mVarType == NamedVariable.COLOR_TYPE) {
+ ret[i++] = n.mVarName;
+ }
+ }
+ }
+ return ret;
+ }
+
//////////////////////////////////////////////////////////////////////////
// Painting
//////////////////////////////////////////////////////////////////////////
@@ -493,6 +526,7 @@
/**
* Returns > 0 if it needs to repaint
+ *
* @return
*/
public int needsRepaint() {
@@ -525,7 +559,6 @@
context.loadFloat(RemoteContext.ID_WINDOW_WIDTH, getWidth());
context.loadFloat(RemoteContext.ID_WINDOW_HEIGHT, getHeight());
mRepaintNext = context.updateOps();
-
for (Operation op : mOperations) {
// operations will only be executed if no theme is set (ie UNSPECIFIED)
// or the theme is equal as the one passed in argument to paint.
diff --git a/core/java/com/android/internal/widget/remotecompose/core/Operations.java b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
index 4b45ab6..fc8668e 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/Operations.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/Operations.java
@@ -19,6 +19,7 @@
import com.android.internal.widget.remotecompose.core.operations.ClickArea;
import com.android.internal.widget.remotecompose.core.operations.ClipPath;
import com.android.internal.widget.remotecompose.core.operations.ClipRect;
+import com.android.internal.widget.remotecompose.core.operations.ColorConstant;
import com.android.internal.widget.remotecompose.core.operations.ColorExpression;
import com.android.internal.widget.remotecompose.core.operations.DrawArc;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
@@ -42,6 +43,7 @@
import com.android.internal.widget.remotecompose.core.operations.MatrixScale;
import com.android.internal.widget.remotecompose.core.operations.MatrixSkew;
import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate;
+import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
import com.android.internal.widget.remotecompose.core.operations.PaintData;
import com.android.internal.widget.remotecompose.core.operations.PathData;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
@@ -105,6 +107,8 @@
public static final int COLOR_EXPRESSIONS = 134;
public static final int TEXT_FROM_FLOAT = 135;
public static final int TEXT_MERGE = 136;
+ public static final int NAMED_VARIABLE = 137;
+ public static final int COLOR_CONSTANT = 138;
/////////////////////////////////////////======================
public static IntMap<CompanionOperation> map = new IntMap<>();
@@ -147,7 +151,8 @@
map.put(COLOR_EXPRESSIONS, ColorExpression.COMPANION);
map.put(TEXT_FROM_FLOAT, TextFromFloat.COMPANION);
map.put(TEXT_MERGE, TextMerge.COMPANION);
-
+ map.put(NAMED_VARIABLE, NamedVariable.COMPANION);
+ map.put(COLOR_CONSTANT, ColorConstant.COMPANION);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
index 52fc314..d462c7d 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeBuffer.java
@@ -19,6 +19,7 @@
import com.android.internal.widget.remotecompose.core.operations.ClickArea;
import com.android.internal.widget.remotecompose.core.operations.ClipPath;
import com.android.internal.widget.remotecompose.core.operations.ClipRect;
+import com.android.internal.widget.remotecompose.core.operations.ColorConstant;
import com.android.internal.widget.remotecompose.core.operations.ColorExpression;
import com.android.internal.widget.remotecompose.core.operations.DrawArc;
import com.android.internal.widget.remotecompose.core.operations.DrawBitmap;
@@ -42,6 +43,7 @@
import com.android.internal.widget.remotecompose.core.operations.MatrixScale;
import com.android.internal.widget.remotecompose.core.operations.MatrixSkew;
import com.android.internal.widget.remotecompose.core.operations.MatrixTranslate;
+import com.android.internal.widget.remotecompose.core.operations.NamedVariable;
import com.android.internal.widget.remotecompose.core.operations.PaintData;
import com.android.internal.widget.remotecompose.core.operations.PathData;
import com.android.internal.widget.remotecompose.core.operations.RootContentBehavior;
@@ -899,6 +901,20 @@
}
/**
+ * Add a simple color
+ * @param color
+ * @return id that represents that color
+ */
+ public int addColor(int color) {
+ ColorConstant c = new ColorConstant(0, color);
+ short id = (short) mRemoteComposeState.cache(c);
+ c.mColorId = id;
+ c.write(mBuffer);
+ return id;
+ }
+
+
+ /**
* Add a color that represents the tween between two colors
* @param color1
* @param color2
@@ -1013,5 +1029,14 @@
return FloatAnimation.packToFloatArray(duration, type, spec, initialValue, wrap);
}
+ /**
+ * This defines the name of the color given the id.
+ * @param id of the color
+ * @param name Name of the color
+ */
+ public void setColorName(int id, String name) {
+ NamedVariable.COMPANION.apply(mBuffer, id,
+ NamedVariable.COLOR_TYPE, name);
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
index 66a37e67..bfe67c8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteComposeState.java
@@ -33,11 +33,13 @@
public class RemoteComposeState {
public static final int START_ID = 42;
private static final int MAX_FLOATS = 500;
+ private static final int MAX_COLORS = 200;
private final IntMap<Object> mIntDataMap = new IntMap<>();
private final IntMap<Boolean> mIntWrittenMap = new IntMap<>();
private final HashMap<Object, Integer> mDataIntMap = new HashMap();
private final float[] mFloatMap = new float[MAX_FLOATS]; // efficient cache
- private final int[] mColorMap = new int[MAX_FLOATS]; // efficient cache
+ private final int[] mColorMap = new int[MAX_COLORS]; // efficient cache
+ private final boolean[] mColorOverride = new boolean[MAX_COLORS];
private int mNextId = START_ID;
{
@@ -49,6 +51,7 @@
/**
* Get Object based on id. The system will cache things like bitmaps
* Paths etc. They can be accessed with this command
+ *
* @param id
* @return
*/
@@ -58,6 +61,7 @@
/**
* true if the cache contain this id
+ *
* @param id
* @return
*/
@@ -150,9 +154,32 @@
* @param color
*/
public void updateColor(int id, int color) {
+ if (mColorOverride[id]) {
+ return;
+ }
mColorMap[id] = color;
}
+ /**
+ * Adds a colorOverride.
+ * This is a list of ids and there colors optimized for playback;
+ *
+ * @param id
+ * @param color
+ */
+ public void overrideColor(int id, int color) {
+ mColorOverride[id] = true;
+ mColorMap[id] = color;
+ }
+
+ /**
+ * Clear the color Overrides
+ */
+ public void clearColorOverride() {
+ for (int i = 0; i < mColorOverride.length; i++) {
+ mColorOverride[i] = false;
+ }
+ }
/**
* Method to determine if a cached value has been written to the documents WireBuffer based on
diff --git a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
index 7e72168..32027d8 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/RemoteContext.java
@@ -72,6 +72,14 @@
return (System.nanoTime() - mStart) * 1E-9f;
}
+ /**
+ * Set the value of a named Color.
+ * This overrides the color in the document
+ * @param colorName
+ * @param color
+ */
+ public abstract void setNamedColorOverride(String colorName, int color);
+
/**
* The context can be used in a few different mode, allowing operations to skip being executed:
@@ -262,16 +270,45 @@
public static final int ID_COMPONENT_WIDTH = 7;
public static final int ID_COMPONENT_HEIGHT = 8;
public static final int ID_CALENDAR_MONTH = 9;
+ public static final int ID_OFFSET_TO_UTC = 10;
+ public static final int ID_WEEK_DAY = 11;
+ public static final int ID_DAY_OF_MONTH = 12;
+ /**
+ * CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600
+ */
public static final float FLOAT_CONTINUOUS_SEC = Utils.asNan(ID_CONTINUOUS_SEC);
+ /**
+ * seconds run from Midnight=0 quantized to seconds hour 0..3599
+ */
public static final float FLOAT_TIME_IN_SEC = Utils.asNan(ID_TIME_IN_SEC);
+ /**
+ * minutes run from Midnight=0 quantized to minutes 0..1439
+ */
public static final float FLOAT_TIME_IN_MIN = Utils.asNan(ID_TIME_IN_MIN);
+ /**
+ * hours run from Midnight=0 quantized to Hours 0-23
+ */
public static final float FLOAT_TIME_IN_HR = Utils.asNan(ID_TIME_IN_HR);
+ /**
+ * Moth of Year quantized to MONTHS 1-12. 1 = January
+ */
public static final float FLOAT_CALENDAR_MONTH = Utils.asNan(ID_CALENDAR_MONTH);
+ /**
+ * DAY OF THE WEEK 1-7. 1 = Monday
+ */
+ public static final float FLOAT_WEEK_DAY = Utils.asNan(ID_WEEK_DAY);
+ /**
+ * DAY OF THE MONTH 1-31
+ */
+ public static final float FLOAT_DAY_OF_MONTH = Utils.asNan(ID_DAY_OF_MONTH);
+
public static final float FLOAT_WINDOW_WIDTH = Utils.asNan(ID_WINDOW_WIDTH);
public static final float FLOAT_WINDOW_HEIGHT = Utils.asNan(ID_WINDOW_HEIGHT);
public static final float FLOAT_COMPONENT_WIDTH = Utils.asNan(ID_COMPONENT_WIDTH);
public static final float FLOAT_COMPONENT_HEIGHT = Utils.asNan(ID_COMPONENT_HEIGHT);
+ // ID_OFFSET_TO_UTC is the offset from UTC in sec (typically / 3600f)
+ public static final float FLOAT_OFFSET_TO_UTC = Utils.asNan(ID_OFFSET_TO_UTC);
///////////////////////////////////////////////////////////////////////////////////////////////
// Click handling
///////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
index e9708b7..04e04bbb 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/TimeVariables.java
@@ -16,6 +16,9 @@
package com.android.internal.widget.remotecompose.core;
import java.time.LocalDateTime;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
/**
* This generates the standard system variables for time.
@@ -23,6 +26,7 @@
public class TimeVariables {
/**
* This class populates all time variables in the system
+ *
* @param context
*/
public void updateTime(RemoteContext context) {
@@ -33,19 +37,29 @@
// hours run from Midnight=0 quantized to Hours 0-23
// CONTINUOUS_SEC is seconds from midnight looping every hour 0-3600
// CONTINUOUS_SEC is accurate to milliseconds due to float precession
- int month = dateTime.getDayOfMonth();
+ // ID_OFFSET_TO_UTC is the offset from UTC in sec (typically / 3600f)
+ int month = dateTime.getMonth().getValue();
int hour = dateTime.getHour();
int minute = dateTime.getMinute();
int seconds = dateTime.getSecond();
int currentMinute = hour * 60 + minute;
int currentSeconds = minute * 60 + seconds;
float sec = currentSeconds + dateTime.getNano() * 1E-9f;
+ int day_week = dateTime.getDayOfWeek().getValue();
+
+ ZoneId zone = ZoneId.systemDefault();
+ OffsetDateTime offsetDateTime = dateTime.atZone(zone).toOffsetDateTime();
+ ZoneOffset offset = offsetDateTime.getOffset();
+
+ context.loadFloat(RemoteContext.ID_OFFSET_TO_UTC, offset.getTotalSeconds());
context.loadFloat(RemoteContext.ID_CONTINUOUS_SEC, sec);
context.loadFloat(RemoteContext.ID_TIME_IN_SEC, currentSeconds);
context.loadFloat(RemoteContext.ID_TIME_IN_MIN, currentMinute);
context.loadFloat(RemoteContext.ID_TIME_IN_HR, hour);
context.loadFloat(RemoteContext.ID_CALENDAR_MONTH, month);
+ context.loadFloat(RemoteContext.ID_DAY_OF_MONTH, month);
+ context.loadFloat(RemoteContext.ID_WEEK_DAY, day_week);
}
}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
new file mode 100644
index 0000000..15c208f
--- /dev/null
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/ColorConstant.java
@@ -0,0 +1,94 @@
+/*
+ * 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.internal.widget.remotecompose.core.operations;
+
+import com.android.internal.widget.remotecompose.core.CompanionOperation;
+import com.android.internal.widget.remotecompose.core.Operation;
+import com.android.internal.widget.remotecompose.core.Operations;
+import com.android.internal.widget.remotecompose.core.RemoteContext;
+import com.android.internal.widget.remotecompose.core.WireBuffer;
+
+import java.util.List;
+
+/**
+ * Operation that defines a simple Color based on ID
+ * Mainly for colors in theming.
+ */
+public class ColorConstant implements Operation {
+ public int mColorId;
+ public int mColor;
+ public static final Companion COMPANION = new Companion();
+
+ public ColorConstant(int colorId, int color) {
+ this.mColorId = colorId;
+ this.mColor = color;
+ }
+
+ @Override
+ public void write(WireBuffer buffer) {
+ COMPANION.apply(buffer, mColorId, mColor);
+ }
+
+ @Override
+ public String toString() {
+ return "ColorConstant[" + mColorId + "] = " + Utils.colorInt(mColor) + "";
+ }
+
+ public static class Companion implements CompanionOperation {
+ private Companion() {
+ }
+
+ @Override
+ public String name() {
+ return "ColorConstant";
+ }
+
+ @Override
+ public int id() {
+ return Operations.COLOR_CONSTANT;
+ }
+
+ /**
+ * Writes out the operation to the buffer
+ *
+ * @param buffer
+ * @param colorId
+ * @param color
+ */
+ public void apply(WireBuffer buffer, int colorId, int color) {
+ buffer.start(Operations.COLOR_CONSTANT);
+ buffer.writeInt(colorId);
+ buffer.writeInt(color);
+ }
+
+ @Override
+ public void read(WireBuffer buffer, List<Operation> operations) {
+ int colorId = buffer.readInt();
+ int color = buffer.readInt();
+ operations.add(new ColorConstant(colorId, color));
+ }
+ }
+
+ @Override
+ public void apply(RemoteContext context) {
+ context.loadColor(mColorId, mColor);
+ }
+
+ @Override
+ public String deepToString(String indent) {
+ return indent + toString();
+ }
+}
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
index 0c5b286..ae27f5f 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/NamedVariable.java
@@ -32,7 +32,9 @@
public int mVarType;
public static final Companion COMPANION = new Companion();
public static final int MAX_STRING_SIZE = 4000;
-
+ public static final int COLOR_TYPE = 2;
+ public static final int FLOAT_TYPE = 1;
+ public static final int STRING_TYPE = 0;
public NamedVariable(int varId, int varType, String name) {
this.mVarId = varId;
this.mVarType = varType;
@@ -72,7 +74,7 @@
* @param text
*/
public void apply(WireBuffer buffer, int varId, int varType, String text) {
- buffer.start(Operations.DATA_TEXT);
+ buffer.start(Operations.NAMED_VARIABLE);
buffer.writeInt(varId);
buffer.writeInt(varType);
buffer.writeUTF8(text);
diff --git a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
index fdc6860..fcb3bfa 100644
--- a/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
+++ b/core/java/com/android/internal/widget/remotecompose/core/operations/Utils.java
@@ -78,7 +78,9 @@
*/
public static void log(String str) {
StackTraceElement s = new Throwable().getStackTrace()[1];
- System.out.println("(" + s.getFileName() + ":" + s.getLineNumber() + ")." + str);
+ System.out.println("(" + s.getFileName()
+ + ":" + s.getLineNumber() + "). "
+ + s.getMethodName() + "() " + str);
}
/**
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
index d1c4d46..a42c584 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposeDocument.java
@@ -103,5 +103,15 @@
return "Document{\n"
+ mDocument + '}';
}
+
+ /**
+ * Gets a array of Names of the named colors defined in the loaded doc.
+ *
+ * @return
+ */
+ public String[] getNamedColors() {
+ return mDocument.getNamedColors();
+ }
+
}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
index 7423a16..73e94fa 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/RemoteComposePlayer.java
@@ -16,9 +16,11 @@
package com.android.internal.widget.remotecompose.player;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.Log;
+import android.util.TypedValue;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
@@ -53,6 +55,7 @@
/**
* Turn on debug information
+ *
* @param debugFlags 1 to set debug on
*/
public void setDebug(int debugFlags) {
@@ -79,6 +82,7 @@
} else {
mInner.setDocument(null);
}
+ mapColors();
}
/**
@@ -106,7 +110,8 @@
LayoutParams.MATCH_PARENT);
addView(horizontalScrollView, layoutParams);
}
- } break;
+ }
+ break;
case RootContentBehavior.SCROLL_VERTICAL: {
if (!(mInner.getParent() instanceof ScrollView)) {
((ViewGroup) mInner.getParent()).removeView(mInner);
@@ -123,9 +128,10 @@
LayoutParams.MATCH_PARENT);
addView(scrollView, layoutParams);
}
- } break;
+ }
+ break;
default:
- if (mInner.getParent() != this) {
+ if (mInner.getParent() != this) {
((ViewGroup) mInner.getParent()).removeView(mInner);
removeAllViews();
LayoutParams layoutParams = new LayoutParams(
@@ -178,5 +184,230 @@
mInner.invalidate();
}
}
+
+ /**
+ * This returns a list of colors that have names in the Document.
+ *
+ * @return
+ */
+ public String[] getNamedColors() {
+ return mInner.getNamedColors();
+ }
+
+ /**
+ * This sets a color based on its name. Overriding the color set in
+ * the document.
+ *
+ * @param colorName Name of the color
+ * @param colorValue The new color value
+ */
+ public void setColor(String colorName, int colorValue) {
+ mInner.setColor(colorName, colorValue);
+ }
+
+ private void mapColors() {
+ String[] name = getNamedColors();
+
+ // make every effort to terminate early
+ if (name == null) {
+ return;
+ }
+ boolean found = false;
+ for (int i = 0; i < name.length; i++) {
+ if (name[i].startsWith("android.")) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return;
+ }
+
+ for (int i = 0; i < name.length; i++) {
+ String s = name[i];
+ if (!s.startsWith("android.")) {
+ continue;
+ }
+ String sub = s.substring("android.".length());
+ switch (sub) {
+ case "actionBarItemBackground":
+ setRColor(s, android.R.attr.actionBarItemBackground);
+ break;
+ case "actionModeBackground":
+ setRColor(s, android.R.attr.actionModeBackground);
+ break;
+ case "actionModeSplitBackground":
+ setRColor(s, android.R.attr.actionModeSplitBackground);
+ break;
+ case "activatedBackgroundIndicator":
+ setRColor(s, android.R.attr.activatedBackgroundIndicator);
+ break;
+ case "colorAccent": // Highlight color for interactive elements
+ setRColor(s, android.R.attr.colorAccent);
+ break;
+ case "colorActivatedHighlight":
+ setRColor(s, android.R.attr.colorActivatedHighlight);
+ break;
+ case "colorBackground": // background color for the app’s window
+ setRColor(s, android.R.attr.colorBackground);
+ break;
+ case "colorBackgroundCacheHint":
+ setRColor(s, android.R.attr.colorBackgroundCacheHint);
+ break;
+ // Background color for floating elements
+ case "colorBackgroundFloating":
+ setRColor(s, android.R.attr.colorBackgroundFloating);
+ break;
+ case "colorButtonNormal": // The default color for buttons
+ setRColor(s, android.R.attr.colorButtonNormal);
+ break;
+ // Color for activated (checked) state of controls.
+ case "colorControlActivated":
+ setRColor(s, android.R.attr.colorControlActivated);
+ break;
+ case "colorControlHighlight": // Color for highlights on controls
+ setRColor(s, android.R.attr.colorControlHighlight);
+ break;
+ // Default color for controls in their normal state.
+ case "colorControlNormal":
+ setRColor(s, android.R.attr.colorControlNormal);
+ break;
+ // Color for edge effects (e.g., overscroll glow)
+ case "colorEdgeEffect":
+ setRColor(s, android.R.attr.colorEdgeEffect);
+ break;
+ case "colorError":
+ setRColor(s, android.R.attr.colorError);
+ break;
+ case "colorFocusedHighlight":
+ setRColor(s, android.R.attr.colorFocusedHighlight);
+ break;
+ case "colorForeground": // General foreground color for views.
+ setRColor(s, android.R.attr.colorForeground);
+ break;
+ // Foreground color for inverse backgrounds.
+ case "colorForegroundInverse":
+ setRColor(s, android.R.attr.colorForegroundInverse);
+ break;
+ case "colorLongPressedHighlight":
+ setRColor(s, android.R.attr.colorLongPressedHighlight);
+ break;
+ case "colorMultiSelectHighlight":
+ setRColor(s, android.R.attr.colorMultiSelectHighlight);
+ break;
+ case "colorPressedHighlight":
+ setRColor(s, android.R.attr.colorPressedHighlight);
+ break;
+ case "colorPrimary": // The primary branding color for the app.
+ setRColor(s, android.R.attr.colorPrimary);
+ break;
+ case "colorPrimaryDark": // darker variant of the primary color
+ setRColor(s, android.R.attr.colorPrimaryDark);
+ break;
+ case "colorSecondary":
+ setRColor(s, android.R.attr.colorSecondary);
+ break;
+ case "detailsElementBackground":
+ setRColor(s, android.R.attr.detailsElementBackground);
+ break;
+ case "editTextBackground":
+ setRColor(s, android.R.attr.editTextBackground);
+ break;
+ case "galleryItemBackground":
+ setRColor(s, android.R.attr.galleryItemBackground);
+ break;
+ case "headerBackground":
+ setRColor(s, android.R.attr.headerBackground);
+ break;
+ case "itemBackground":
+ setRColor(s, android.R.attr.itemBackground);
+ break;
+ case "numbersBackgroundColor":
+ setRColor(s, android.R.attr.numbersBackgroundColor);
+ break;
+ case "panelBackground":
+ setRColor(s, android.R.attr.panelBackground);
+ break;
+ case "panelColorBackground":
+ setRColor(s, android.R.attr.panelColorBackground);
+ break;
+ case "panelFullBackground":
+ setRColor(s, android.R.attr.panelFullBackground);
+ break;
+ case "popupBackground":
+ setRColor(s, android.R.attr.popupBackground);
+ break;
+ case "queryBackground":
+ setRColor(s, android.R.attr.queryBackground);
+ break;
+ case "selectableItemBackground":
+ setRColor(s, android.R.attr.selectableItemBackground);
+ break;
+ case "submitBackground":
+ setRColor(s, android.R.attr.submitBackground);
+ break;
+ case "textColor":
+ setRColor(s, android.R.attr.textColor);
+ break;
+ case "windowBackground":
+ setRColor(s, android.R.attr.windowBackground);
+ break;
+ case "windowBackgroundFallback":
+ setRColor(s, android.R.attr.windowBackgroundFallback);
+ break;
+ // Primary text color for inverse backgrounds
+ case "textColorPrimaryInverse":
+ setRColor(s, android.R.attr.textColorPrimaryInverse);
+ break;
+ // Secondary text color for inverse backgrounds
+ case "textColorSecondaryInverse":
+ setRColor(s, android.R.attr.textColorSecondaryInverse);
+ break;
+ // Tertiary text color for less important text.
+ case "textColorTertiary":
+ setRColor(s, android.R.attr.textColorTertiary);
+ break;
+ // Tertiary text color for inverse backgrounds
+ case "textColorTertiaryInverse":
+ setRColor(s, android.R.attr.textColorTertiaryInverse);
+ break;
+ // Text highlight color (e.g., selected text background).
+ case "textColorHighlight":
+ setRColor(s, android.R.attr.textColorHighlight);
+ break;
+ // Color for hyperlinks.
+ case "textColorLink":
+ setRColor(s, android.R.attr.textColorLink);
+ break;
+ // Color for hint text.
+ case "textColorHint":
+ setRColor(s, android.R.attr.textColorHint);
+ break;
+ // text color for inverse backgrounds..
+ case "textColorHintInverse":
+ setRColor(s, android.R.attr.textColorHintInverse);
+ break;
+ // Default color for the thumb of switches.
+ case "colorSwitchThumbNormal":
+ setRColor(s, android.R.attr.colorControlNormal);
+ break;
+ }
+ }
+ }
+
+ private void setRColor(String name, int id) {
+ int color = getColorFromResource(id);
+ setColor(name, color);
+ }
+
+ private int getColorFromResource(int id) {
+ TypedValue typedValue = new TypedValue();
+ try (TypedArray arr = getContext()
+ .getApplicationContext()
+ .obtainStyledAttributes(typedValue.data, new int[]{id})) {
+ int color = arr.getColor(0, -1);
+ return color;
+ }
+ }
}
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
index 6e4893b..dd43bd5 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/AndroidRemoteContext.java
@@ -76,6 +76,16 @@
}
/**
+ * Override a color to force it to be the color provided
+ *
+ * @param colorName name of color
+ * @param color
+ */
+ public void setNamedColorOverride(String colorName, int color) {
+ int id = mVarNameHashMap.get(colorName).mId;
+ mRemoteComposeState.overrideColor(id, color);
+ }
+ /**
* Decode a byte array into an image and cache it using the given imageId
*
* @param width with of image to be loaded
diff --git a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
index 97d23c8..a2f79cc 100644
--- a/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
+++ b/core/java/com/android/internal/widget/remotecompose/player/platform/RemoteComposeCanvas.java
@@ -42,6 +42,7 @@
boolean mInActionDown = false;
boolean mDebug = false;
Point mActionDownPoint = new Point(0, 0);
+ AndroidRemoteContext mARContext = new AndroidRemoteContext();
public RemoteComposeCanvas(Context context) {
super(context);
@@ -88,8 +89,6 @@
invalidate();
}
- AndroidRemoteContext mARContext = new AndroidRemoteContext();
-
@Override
public void onViewAttachedToWindow(View view) {
if (mDocument == null) {
@@ -120,6 +119,20 @@
removeAllViews();
}
+ public String[] getNamedColors() {
+ return mDocument.getNamedColors();
+ }
+
+ /**
+ * set the color associated with this name.
+ *
+ * @param colorName Name of color typically "android.xxx"
+ * @param colorValue "the argb value"
+ */
+ public void setColor(String colorName, int colorValue) {
+ mARContext.setNamedColorOverride(colorName, colorValue);
+ }
+
public interface ClickCallbacks {
void click(int id, String metadata);
}
diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java
index 6b3cf7b..ee1b658 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.runner.RunWith;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@@ -382,8 +381,7 @@
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 @@
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 @@
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 @@
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/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index b153700..9337bf6 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -16,13 +16,6 @@
package android.view;
-import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
-import static android.view.flags.Flags.FLAG_ADD_SCHANDLE_TO_VRI_SURFACE;
-import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
-import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
-import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY;
-import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY;
-import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
import static android.view.Surface.FRAME_RATE_CATEGORY_DEFAULT;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH_HINT;
@@ -44,6 +37,13 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
+import static android.view.flags.Flags.FLAG_ADD_SCHANDLE_TO_VRI_SURFACE;
+import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_BY_SIZE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_FUNCTION_ENABLING_READ_ONLY;
+import static android.view.flags.Flags.FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY;
+import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
import static android.view.flags.Flags.toolkitFrameRateBySizeReadOnly;
import static android.view.flags.Flags.toolkitFrameRateDefaultNormalReadOnly;
import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly;
@@ -53,6 +53,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -63,9 +64,11 @@
import android.app.UiModeManager;
import android.content.Context;
import android.graphics.ForceDarkType;
+import android.graphics.Rect;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.SystemProperties;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
@@ -1540,6 +1543,37 @@
nativeCreateASurfaceControlFromSurface(mViewRootImpl.mSurface));
}
+ @EnableFlags(Flags.FLAG_INSETS_CONTROL_SEQ)
+ @Test
+ public void testHandleInsetsControlChanged() {
+ mView = new View(sContext);
+ attachViewToWindow(mView);
+
+ mViewRootImpl = mView.getViewRootImpl();
+ final InsetsController controller = mViewRootImpl.getInsetsController();
+
+ final InsetsState state0 = new InsetsState();
+ final InsetsState state1 = new InsetsState();
+ state0.setDisplayFrame(new Rect(0, 0, 500, 1000));
+ state0.setSeq(10000);
+ state1.setDisplayFrame(new Rect(0, 0, 1500, 2000));
+ state1.setSeq(10001);
+ final InsetsSourceControl.Array array = new InsetsSourceControl.Array();
+
+ sInstrumentation.runOnMainSync(() -> {
+ mViewRootImpl.handleInsetsControlChanged(state0, array);
+ assertEquals(state0, controller.getLastDispatchedState());
+
+ mViewRootImpl.handleInsetsControlChanged(state1, array);
+ assertEquals(state1, controller.getLastDispatchedState());
+
+ // Skip the stale value.
+ mViewRootImpl.handleInsetsControlChanged(state0, array);
+ assertEquals(state1, controller.getLastDispatchedState());
+ assertNotEquals(state0, controller.getLastDispatchedState());
+ });
+ }
+
private boolean setForceDarkSysProp(boolean isForceDarkEnabled) {
try {
SystemProperties.set(
diff --git a/data/keyboards/Vendor_18d1_Product_4f60.idc b/data/keyboards/Vendor_18d1_Product_4f60.idc
new file mode 100644
index 0000000..b9fd406
--- /dev/null
+++ b/data/keyboards/Vendor_18d1_Product_4f60.idc
@@ -0,0 +1,18 @@
+# Copyright 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.
+
+# Increase palm thresholds, since this touchpad has a tendency to overstate
+# touch sizes.
+gestureProp.Palm_Width = 40.0
+gestureProp.Multiple_Palm_Width = 40.0
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 3b7eb29..3ff40e0 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -131,3 +131,10 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "enable_bubble_bar_in_persistent_task_bar"
+ namespace: "multitasking"
+ description: "Enable bubble bar to be shown in the persistent task bar"
+ bug: "346391377"
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 1fcfa7f..43cdcca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.dagger;
import android.annotation.Nullable;
+import android.app.KeyguardManager;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.os.Handler;
@@ -514,6 +515,7 @@
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
DragAndDropController dragAndDropController,
Transitions transitions,
+ KeyguardManager keyguardManager,
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
@@ -528,7 +530,7 @@
Optional<RecentTasksController> recentTasksController) {
return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
- dragAndDropController, transitions, enterDesktopTransitionHandler,
+ dragAndDropController, transitions, keyguardManager, enterDesktopTransitionHandler,
exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
dragToDesktopTransitionHandler, desktopModeTaskRepository,
desktopModeLoggerTransitionObserver, launchAdjacentController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 1965382..5813f85 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -18,6 +18,7 @@
import android.app.ActivityManager.RunningTaskInfo
import android.app.ActivityOptions
+import android.app.KeyguardManager
import android.app.PendingIntent
import android.app.TaskInfo
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
@@ -108,6 +109,7 @@
private val rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer,
private val dragAndDropController: DragAndDropController,
private val transitions: Transitions,
+ private val keyguardManager: KeyguardManager,
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
@@ -972,6 +974,12 @@
transition: IBinder
): WindowContainerTransaction? {
KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: handleFreeformTaskLaunch")
+ if (keyguardManager.isKeyguardLocked) {
+ // Do NOT handle freeform task launch when locked.
+ // It will be launched in fullscreen windowing mode (Details: b/160925539)
+ KtProtoLog.v(WM_SHELL_DESKTOP_MODE, "DesktopTasksController: skip keyguard is locked")
+ return null
+ }
if (!desktopModeTaskRepository.isDesktopModeShowing(task.displayId)) {
KtProtoLog.d(
WM_SHELL_DESKTOP_MODE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
index 7c5f10a..8ee72b4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/TaskStackTransitionObserver.kt
@@ -76,21 +76,40 @@
continue
}
+ // Filter out changes that we care about
if (change.mode == WindowManager.TRANSIT_OPEN) {
change.taskInfo?.let { taskInfoList.add(it) }
transitionTypeList.add(change.mode)
}
}
- transitionToTransitionChanges.put(
- transition,
- TransitionChanges(taskInfoList, transitionTypeList)
- )
+ // Only add the transition to map if it has a change we care about
+ if (taskInfoList.isNotEmpty()) {
+ transitionToTransitionChanges.put(
+ transition,
+ TransitionChanges(taskInfoList, transitionTypeList)
+ )
+ }
}
}
override fun onTransitionStarting(transition: IBinder) {}
- override fun onTransitionMerged(merged: IBinder, playing: IBinder) {}
+ override fun onTransitionMerged(merged: IBinder, playing: IBinder) {
+ val mergedTransitionChanges =
+ transitionToTransitionChanges.get(merged)
+ ?:
+ // We are adding changes of the merged transition to changes of the playing
+ // transition so if there is no changes nothing to do.
+ return
+
+ transitionToTransitionChanges.remove(merged)
+ val playingTransitionChanges = transitionToTransitionChanges.get(playing)
+ if (playingTransitionChanges != null) {
+ playingTransitionChanges.merge(mergedTransitionChanges)
+ } else {
+ transitionToTransitionChanges.put(playing, mergedTransitionChanges)
+ }
+ }
override fun onTransitionFinished(transition: IBinder, aborted: Boolean) {
val taskInfoList =
@@ -138,6 +157,11 @@
private data class TransitionChanges(
val taskInfoList: MutableList<RunningTaskInfo> = ArrayList(),
- val transitionTypeList: MutableList<Int> = ArrayList()
- )
+ val transitionTypeList: MutableList<Int> = ArrayList(),
+ ) {
+ fun merge(transitionChanges: TransitionChanges) {
+ taskInfoList.addAll(transitionChanges.taskInfoList)
+ transitionTypeList.addAll(transitionChanges.transitionTypeList)
+ }
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 9412b2b..9db153f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -53,6 +53,7 @@
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CHANGE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_CLOSE;
import static com.android.internal.policy.TransitionAnimation.WALLPAPER_TRANSITION_INTRA_OPEN;
@@ -944,12 +945,15 @@
}
private static int getWallpaperTransitType(TransitionInfo info) {
+ boolean hasWallpaper = false;
boolean hasOpenWallpaper = false;
boolean hasCloseWallpaper = false;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0) {
+ if ((change.getFlags() & FLAG_SHOW_WALLPAPER) != 0
+ || (change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ hasWallpaper = true;
if (TransitionUtil.isOpeningType(change.getMode())) {
hasOpenWallpaper = true;
} else if (TransitionUtil.isClosingType(change.getMode())) {
@@ -965,6 +969,8 @@
return WALLPAPER_TRANSITION_OPEN;
} else if (hasCloseWallpaper) {
return WALLPAPER_TRANSITION_CLOSE;
+ } else if (hasWallpaper) {
+ return WALLPAPER_TRANSITION_CHANGE;
} else {
return WALLPAPER_TRANSITION_NONE;
}
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 d2760ff..f6e38da 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,15 @@
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_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;
@@ -519,12 +522,17 @@
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();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 14fa0f1..0e53e10 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -18,6 +18,7 @@
import android.app.ActivityManager.RecentTaskInfo
import android.app.ActivityManager.RunningTaskInfo
+import android.app.KeyguardManager
import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
@@ -149,6 +150,7 @@
@Mock lateinit var syncQueue: SyncTransactionQueue
@Mock lateinit var rootTaskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer
@Mock lateinit var transitions: Transitions
+ @Mock lateinit var keyguardManager: KeyguardManager
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
@Mock
@@ -233,6 +235,7 @@
rootTaskDisplayAreaOrganizer,
dragAndDropController,
transitions,
+ keyguardManager,
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
toggleResizeDesktopTaskTransitionHandler,
@@ -1301,6 +1304,17 @@
}
@Test
+ fun handleRequest_freeformTask_keyguardLocked_returnNull() {
+ assumeTrue(ENABLE_SHELL_TRANSITIONS)
+ whenever(keyguardManager.isKeyguardLocked).thenReturn(true)
+ val freeformTask = createFreeformTask(displayId = DEFAULT_DISPLAY)
+
+ val result = controller.handleRequest(Binder(), createTransition(freeformTask))
+
+ assertNull(result, "Should NOT handle request")
+ }
+
+ @Test
fun handleRequest_notOpenOrToFrontTransition_returnNull() {
assumeTrue(ENABLE_SHELL_TRANSITIONS)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
index f959970..0e5efa6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/TaskStackTransitionObserverTest.kt
@@ -48,7 +48,6 @@
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
-
/**
* Test class for {@link TaskStackTransitionObserver}
*
@@ -168,6 +167,80 @@
.isEqualTo(freeformOpenChange.taskInfo?.windowingMode)
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun transitionMerged_withChange_onlyOpenChangeIsNotified() {
+ val listener = TestListener()
+ val executor = TestShellExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Create open transition
+ val change =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build()
+
+ // create change transition to be merged to above transition
+ val mergedChange =
+ createChange(
+ WindowManager.TRANSIT_CHANGE,
+ createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val mergedTransitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_CHANGE, 0).addChange(mergedChange).build()
+ val mergedTransition = Mockito.mock(IBinder::class.java)
+
+ callOnTransitionReady(transitionInfo)
+ callOnTransitionReady(mergedTransitionInfo, mergedTransition)
+ callOnTransitionMerged(mergedTransition)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(change.taskInfo?.taskId)
+ assertThat(listener.taskInfoToBeNotified.windowingMode)
+ .isEqualTo(change.taskInfo?.windowingMode)
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_TASK_STACK_OBSERVER_IN_SHELL)
+ fun transitionMerged_withOpen_lastOpenChangeIsNotified() {
+ val listener = TestListener()
+ val executor = TestShellExecutor()
+ transitionObserver.addTaskStackTransitionObserverListener(listener, executor)
+
+ // Create open transition
+ val change =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(1, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val transitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(change).build()
+
+ // create change transition to be merged to above transition
+ val mergedChange =
+ createChange(
+ WindowManager.TRANSIT_OPEN,
+ createTaskInfo(2, WindowConfiguration.WINDOWING_MODE_FREEFORM)
+ )
+ val mergedTransitionInfo =
+ TransitionInfoBuilder(WindowManager.TRANSIT_OPEN, 0).addChange(mergedChange).build()
+ val mergedTransition = Mockito.mock(IBinder::class.java)
+
+ callOnTransitionReady(transitionInfo)
+ callOnTransitionReady(mergedTransitionInfo, mergedTransition)
+ callOnTransitionMerged(mergedTransition)
+ callOnTransitionFinished()
+ executor.flushAll()
+
+ assertThat(listener.taskInfoToBeNotified.taskId).isEqualTo(mergedChange.taskInfo?.taskId)
+ assertThat(listener.taskInfoToBeNotified.windowingMode)
+ .isEqualTo(mergedChange.taskInfo?.windowingMode)
+ }
+
class TestListener : TaskStackTransitionObserver.TaskStackTransitionObserverListener {
var taskInfoToBeNotified = ActivityManager.RunningTaskInfo()
@@ -179,11 +252,14 @@
}
/** Simulate calling the onTransitionReady() method */
- private fun callOnTransitionReady(transitionInfo: TransitionInfo) {
+ private fun callOnTransitionReady(
+ transitionInfo: TransitionInfo,
+ transition: IBinder = mockTransitionBinder
+ ) {
val startT = Mockito.mock(SurfaceControl.Transaction::class.java)
val finishT = Mockito.mock(SurfaceControl.Transaction::class.java)
- transitionObserver.onTransitionReady(mockTransitionBinder, transitionInfo, startT, finishT)
+ transitionObserver.onTransitionReady(transition, transitionInfo, startT, finishT)
}
/** Simulate calling the onTransitionFinished() method */
@@ -191,6 +267,11 @@
transitionObserver.onTransitionFinished(mockTransitionBinder, false)
}
+ /** Simulate calling the onTransitionMerged() method */
+ private fun callOnTransitionMerged(merged: IBinder, playing: IBinder = mockTransitionBinder) {
+ transitionObserver.onTransitionMerged(merged, playing)
+ }
+
companion object {
fun createTaskInfo(taskId: Int, windowingMode: Int): ActivityManager.RunningTaskInfo {
val taskInfo = ActivityManager.RunningTaskInfo()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java
new file mode 100644
index 0000000..b54c3bf
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ChangeBuilder.java
@@ -0,0 +1,72 @@
+/*
+ * 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.wm.shell.transition;
+
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static org.mockito.Mockito.mock;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+
+public class ChangeBuilder {
+ final TransitionInfo.Change mChange;
+
+ ChangeBuilder(@WindowManager.TransitionType int mode) {
+ mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true));
+ mChange.setMode(mode);
+ }
+
+ ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) {
+ mChange.setFlags(flags);
+ return this;
+ }
+
+ ChangeBuilder setTask(RunningTaskInfo taskInfo) {
+ mChange.setTaskInfo(taskInfo);
+ return this;
+ }
+
+ ChangeBuilder setRotate(int anim) {
+ return setRotate(Surface.ROTATION_90, anim);
+ }
+
+ ChangeBuilder setRotate() {
+ return setRotate(ROTATION_ANIMATION_UNSPECIFIED);
+ }
+
+ ChangeBuilder setRotate(@Surface.Rotation int target, int anim) {
+ mChange.setRotation(Surface.ROTATION_0, target);
+ mChange.setRotationAnimation(anim);
+ return this;
+ }
+
+ TransitionInfo.Change build() {
+ return mChange;
+ }
+
+ private static SurfaceControl createMockSurface(boolean valid) {
+ SurfaceControl sc = mock(SurfaceControl.class);
+ doReturn(valid).when(sc).isValid();
+ return sc;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
new file mode 100644
index 0000000..754a173
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/DefaultTransitionHandlerTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.wm.shell.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_SLEEP;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_SYNC;
+import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.WindowContainerToken;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.sysui.ShellInit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the default animation handler that is used if no other special-purpose handler picks
+ * up an animation request.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:DefaultTransitionHandlerTest
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DefaultTransitionHandlerTest extends ShellTestCase {
+
+ private final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ private final DisplayController mDisplayController = mock(DisplayController.class);
+ private final TransactionPool mTransactionPool = new MockTransactionPool();
+ private final TestShellExecutor mMainExecutor = new TestShellExecutor();
+ private final TestShellExecutor mAnimExecutor = new TestShellExecutor();
+ private final Handler mMainHandler = new Handler(Looper.getMainLooper());
+
+ private ShellInit mShellInit;
+ private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private DefaultTransitionHandler mTransitionHandler;
+
+ @Before
+ public void setUp() {
+ mShellInit = new ShellInit(mMainExecutor);
+ mRootTaskDisplayAreaOrganizer = new RootTaskDisplayAreaOrganizer(
+ mMainExecutor,
+ mContext,
+ mShellInit);
+ mTransitionHandler = new DefaultTransitionHandler(
+ mContext, mShellInit, mDisplayController,
+ mTransactionPool, mMainExecutor, mMainHandler, mAnimExecutor,
+ mRootTaskDisplayAreaOrganizer);
+ mShellInit.init();
+ }
+
+ @After
+ public void tearDown() {
+ flushHandlers();
+ }
+
+ private void flushHandlers() {
+ mMainHandler.runWithScissors(() -> {
+ mAnimExecutor.flushAll();
+ mMainExecutor.flushAll();
+ }, 1000L);
+ }
+
+ @Test
+ public void testAnimationBackgroundCreatedForTaskTransition() {
+ final TransitionInfo.Change openTask = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(1))
+ .build();
+ final TransitionInfo.Change closeTask = new ChangeBuilder(TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(2))
+ .build();
+
+ final IBinder token = new Binder();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openTask)
+ .addChange(closeTask)
+ .build();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ mergeSync(mTransitionHandler, token);
+ flushHandlers();
+
+ verify(startT).setColor(any(), any());
+ }
+
+ @Test
+ public void testNoAnimationBackgroundForTranslucentTasks() {
+ final TransitionInfo.Change openTask = new ChangeBuilder(TRANSIT_OPEN)
+ .setTask(createTaskInfo(1))
+ .setFlags(FLAG_TRANSLUCENT)
+ .build();
+ final TransitionInfo.Change closeTask = new ChangeBuilder(TRANSIT_TO_BACK)
+ .setTask(createTaskInfo(2))
+ .setFlags(FLAG_TRANSLUCENT)
+ .build();
+
+ final IBinder token = new Binder();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openTask)
+ .addChange(closeTask)
+ .build();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ mergeSync(mTransitionHandler, token);
+ flushHandlers();
+
+ verify(startT, never()).setColor(any(), any());
+ }
+
+ @Test
+ public void testNoAnimationBackgroundForWallpapers() {
+ final TransitionInfo.Change openWallpaper = new ChangeBuilder(TRANSIT_OPEN)
+ .setFlags(TransitionInfo.FLAG_IS_WALLPAPER)
+ .build();
+ final TransitionInfo.Change closeWallpaper = new ChangeBuilder(TRANSIT_TO_BACK)
+ .setFlags(TransitionInfo.FLAG_IS_WALLPAPER)
+ .build();
+
+ final IBinder token = new Binder();
+ final TransitionInfo info = new TransitionInfoBuilder(TRANSIT_OPEN)
+ .addChange(openWallpaper)
+ .addChange(closeWallpaper)
+ .build();
+ final SurfaceControl.Transaction startT = MockTransactionPool.create();
+ final SurfaceControl.Transaction finishT = MockTransactionPool.create();
+
+ mTransitionHandler.startAnimation(token, info, startT, finishT,
+ mock(Transitions.TransitionFinishCallback.class));
+
+ mergeSync(mTransitionHandler, token);
+ flushHandlers();
+
+ verify(startT, never()).setColor(any(), any());
+ }
+
+ private static void mergeSync(Transitions.TransitionHandler handler, IBinder token) {
+ handler.mergeAnimation(
+ new Binder(),
+ new TransitionInfoBuilder(TRANSIT_SLEEP, FLAG_SYNC).build(),
+ MockTransactionPool.create(),
+ token,
+ mock(Transitions.TransitionFinishCallback.class));
+ }
+
+ private static RunningTaskInfo createTaskInfo(int taskId) {
+ RunningTaskInfo taskInfo = new RunningTaskInfo();
+ taskInfo.taskId = taskId;
+ taskInfo.topActivityType = ACTIVITY_TYPE_STANDARD;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ taskInfo.configuration.windowConfiguration.setActivityType(taskInfo.topActivityType);
+ taskInfo.token = mock(WindowContainerToken.class);
+ return taskInfo;
+ }
+}
+
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java
new file mode 100644
index 0000000..574a87a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java
@@ -0,0 +1,41 @@
+/*
+ * 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.wm.shell.transition;
+
+import static org.mockito.Mockito.RETURNS_SELF;
+import static org.mockito.Mockito.mock;
+
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.util.StubTransaction;
+
+public class MockTransactionPool extends TransactionPool {
+
+ public static SurfaceControl.Transaction create() {
+ return mock(StubTransaction.class, RETURNS_SELF);
+ }
+
+ @Override
+ public SurfaceControl.Transaction acquire() {
+ return create();
+ }
+
+ @Override
+ public void release(SurfaceControl.Transaction t) {
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 69a61ea..8331d59 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -79,7 +79,6 @@
import android.view.IRecentsAnimationRunner;
import android.view.Surface;
import android.view.SurfaceControl;
-import android.view.WindowManager;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.IWindowContainerToken;
@@ -1615,43 +1614,6 @@
eq(R.styleable.WindowAnimation_activityCloseEnterAnimation), anyBoolean());
}
- class ChangeBuilder {
- final TransitionInfo.Change mChange;
-
- ChangeBuilder(@WindowManager.TransitionType int mode) {
- mChange = new TransitionInfo.Change(null /* token */, createMockSurface(true));
- mChange.setMode(mode);
- }
-
- ChangeBuilder setFlags(@TransitionInfo.ChangeFlags int flags) {
- mChange.setFlags(flags);
- return this;
- }
-
- ChangeBuilder setTask(RunningTaskInfo taskInfo) {
- mChange.setTaskInfo(taskInfo);
- return this;
- }
-
- ChangeBuilder setRotate(int anim) {
- return setRotate(Surface.ROTATION_90, anim);
- }
-
- ChangeBuilder setRotate() {
- return setRotate(ROTATION_ANIMATION_UNSPECIFIED);
- }
-
- ChangeBuilder setRotate(@Surface.Rotation int target, int anim) {
- mChange.setRotation(Surface.ROTATION_0, target);
- mChange.setRotationAnimation(anim);
- return this;
- }
-
- TransitionInfo.Change build() {
- return mChange;
- }
- }
-
class TestTransitionHandler implements Transitions.TransitionHandler {
ArrayList<Pair<IBinder, Transitions.TransitionFinishCallback>> mFinishes =
new ArrayList<>();
@@ -1740,12 +1702,6 @@
.addChange(TRANSIT_OPEN).addChange(TRANSIT_CLOSE).build();
}
- private static SurfaceControl createMockSurface(boolean valid) {
- SurfaceControl sc = mock(SurfaceControl.class);
- doReturn(valid).when(sc).isValid();
- return sc;
- }
-
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode, int activityType) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index 3ba0d59..8acaf3b 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -5143,9 +5143,9 @@
* of negative QP and positive QP are chosen wisely, the overall viewing experience can be
* improved.
* <p>
- * If byte array size is too small than the expected size, components may ignore the
- * configuration silently. If the byte array exceeds the expected size, components shall use
- * the initial portion and ignore the rest.
+ * If byte array size is smaller than the expected size, components will ignore the
+ * configuration and print an error message. If the byte array exceeds the expected size,
+ * components will use the initial portion and ignore the rest.
* <p>
* The scope of this key is throughout the encoding session until it is reconfigured during
* running state.
@@ -5159,7 +5159,8 @@
* Set the region of interest as QpOffset-Rects on the next queued input frame.
* <p>
* The associated value is a String in the format "Top1,Left1-Bottom1,Right1=Offset1;Top2,
- * Left2-Bottom2,Right2=Offset2;...". Co-ordinates (Top, Left), (Top, Right), (Bottom, Left)
+ * Left2-Bottom2,Right2=Offset2;...". If the configuration doesn't follow this pattern,
+ * it will be ignored. Co-ordinates (Top, Left), (Top, Right), (Bottom, Left)
* and (Bottom, Right) form the vertices of bounding box of region of interest in pixels.
* Pixel (0, 0) points to the top-left corner of the frame. Offset is the suggested
* quantization parameter (QP) offset of the blocks in the bounding box. The bounding box
@@ -5171,9 +5172,10 @@
* negative QP and positive QP are chosen wisely, the overall viewing experience can be
* improved.
* <p>
- * If Roi rect is not valid that is bounding box width is < 0 or bounding box height is < 0,
- * components may ignore the configuration silently. If Roi rect extends outside frame
- * boundaries, then rect shall be clamped to the frame boundaries.
+ * If roi (region of interest) rect is outside the frame boundaries, that is, left < 0 or
+ * top < 0 or right > width or bottom > height, then rect shall be clamped to the frame
+ * boundaries. If roi rect is not valid, that is left > right or top > bottom, then the
+ * parameter setting is ignored.
* <p>
* The scope of this key is throughout the encoding session until it is reconfigured during
* running state.
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
index 7669e79b..f8c3a93 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS
@@ -1,9 +1,4 @@
# Default reviewers for this and subdirectories.
-siyuanh@google.com
-hughchen@google.com
-timhypeng@google.com
-robertluo@google.com
-songferngwang@google.com
yqian@google.com
chelseahao@google.com
yiyishen@google.com
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
index c5e86b4..4f2329b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/PowerAllowlistBackend.java
@@ -327,4 +327,12 @@
return sInstance;
}
}
+
+ /** Testing only. Reset the instance to avoid tests affecting each other. */
+ @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+ public static void resetInstance() {
+ synchronized (PowerAllowlistBackend.class) {
+ sInstance = 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 8ec5ba1..837c682 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
@@ -46,6 +46,7 @@
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
@@ -98,6 +99,7 @@
private val contentResolver: ContentResolver,
private val backgroundCoroutineContext: CoroutineContext,
private val coroutineScope: CoroutineScope,
+ private val logger: Logger,
) : AudioRepository {
private val streamSettingNames: Map<AudioStream, String> =
@@ -170,6 +172,7 @@
.conflate()
.map { getCurrentAudioStream(audioStream) }
.onStart { emit(getCurrentAudioStream(audioStream)) }
+ .onEach { logger.onVolumeUpdateReceived(audioStream, it) }
.flowOn(backgroundCoroutineContext)
}
@@ -193,6 +196,7 @@
override suspend fun setVolume(audioStream: AudioStream, volume: Int) {
withContext(backgroundCoroutineContext) {
+ logger.onSetVolumeRequested(audioStream, volume)
audioManager.setStreamVolume(audioStream.value, volume, 0)
}
}
@@ -247,4 +251,11 @@
awaitClose { contentResolver.unregisterContentObserver(observer) }
}
}
+
+ interface Logger {
+
+ fun onSetVolumeRequested(audioStream: AudioStream, volume: Int)
+
+ fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel)
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
index 9c48299..c8e4d71 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/shared/model/AudioStream.kt
@@ -17,6 +17,7 @@
package com.android.settingslib.volume.shared.model
import android.media.AudioManager
+import android.media.AudioSystem
/** Type-safe wrapper for [AudioManager] audio stream. */
@JvmInline
@@ -25,6 +26,8 @@
require(value in supportedStreamTypes) { "Unsupported stream=$value" }
}
+ override fun toString(): String = AudioSystem.streamToString(value)
+
companion object {
val supportedStreamTypes =
setOf(
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 844dc12..0e43acb 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
@@ -64,6 +64,7 @@
@Mock private lateinit var communicationDevice: AudioDeviceInfo
@Mock private lateinit var contentResolver: ContentResolver
+ private val logger = FakeAudioRepositoryLogger()
private val eventsReceiver = FakeAudioManagerEventsReceiver()
private val volumeByStream: MutableMap<Int, Int> = mutableMapOf()
private val isAffectedByRingerModeByStream: MutableMap<Int, Boolean> = mutableMapOf()
@@ -109,6 +110,7 @@
contentResolver,
testScope.testScheduler,
testScope.backgroundScope,
+ logger,
)
}
@@ -173,6 +175,15 @@
underTest.setVolume(audioStream, 50)
runCurrent()
+ assertThat(logger.logs)
+ .isEqualTo(
+ listOf(
+ "onVolumeUpdateReceived audioStream=STREAM_SYSTEM",
+ "onSetVolumeRequested audioStream=STREAM_SYSTEM",
+ "onVolumeUpdateReceived audioStream=STREAM_SYSTEM",
+ "onVolumeUpdateReceived audioStream=STREAM_SYSTEM",
+ )
+ )
assertThat(streamModel)
.isEqualTo(
AudioStreamModel(
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt
new file mode 100644
index 0000000..389bf53
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/volume/data/repository/FakeAudioRepositoryLogger.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.settingslib.volume.data.repository
+
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+
+class FakeAudioRepositoryLogger : AudioRepositoryImpl.Logger {
+
+ private val mutableLogs: MutableList<String> = mutableListOf()
+ val logs: List<String>
+ get() = mutableLogs
+
+ override fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) {
+ synchronized(mutableLogs) {
+ mutableLogs.add("onSetVolumeRequested audioStream=$audioStream")
+ }
+ }
+
+ override fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel) {
+ synchronized(mutableLogs) {
+ mutableLogs.add("onVolumeUpdateReceived audioStream=$audioStream")
+ }
+ }
+}
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 186bd7c..ba0d7de 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -175,4 +175,6 @@
<dimen name="sfps_progress_bar_padding_from_edge">7dp</dimen>
<dimen name="keyguard_presentation_width">410dp</dimen>
+
+ <dimen name="appclips_backlinks_icon_size">24dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/drawable/backlinks_rounded_rectangle.xml b/packages/SystemUI/res/drawable/backlinks_rounded_rectangle.xml
new file mode 100644
index 0000000..225f7bd
--- /dev/null
+++ b/packages/SystemUI/res/drawable/backlinks_rounded_rectangle.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ 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.
+ -->
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetBottom="4dp"
+ android:insetTop="4dp">
+ <shape android:shape="rectangle">
+ <corners android:radius="8dp" />
+ <solid android:color="@android:color/system_surface_container_highest_light" />
+ </shape>
+</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/app_clips_screenshot.xml b/packages/SystemUI/res/layout/app_clips_screenshot.xml
index a3af9490..6d4e410 100644
--- a/packages/SystemUI/res/layout/app_clips_screenshot.xml
+++ b/packages/SystemUI/res/layout/app_clips_screenshot.xml
@@ -51,13 +51,30 @@
app:layout_constraintStart_toEndOf="@id/save"
app:layout_constraintTop_toTopOf="parent" />
+ <CheckBox
+ android:id="@+id/backlinks_include_data"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_marginStart="16dp"
+ android:checked="true"
+ android:text="@string/backlinks_include_link"
+ android:visibility="gone"
+ app:layout_constraintBottom_toTopOf="@id/preview"
+ app:layout_constraintStart_toEndOf="@id/cancel"
+ app:layout_constraintTop_toTopOf="parent" />
+
<TextView
android:id="@+id/backlinks_data"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
+ android:layout_height="48dp"
+ android:layout_marginStart="16dp"
+ android:background="@drawable/backlinks_rounded_rectangle"
+ android:drawablePadding="4dp"
+ android:gravity="center"
+ android:paddingHorizontal="8dp"
android:visibility="gone"
- app:layout_constraintStart_toEndOf="@id/cancel"
+ app:layout_constraintBottom_toTopOf="@id/preview"
+ app:layout_constraintStart_toEndOf="@id/backlinks_include_data"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3eacaa1..2f61b12 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -269,8 +269,7 @@
<string name="screenshot_detected_multiple_template"><xliff:g id="appName" example="Google Chrome">%1$s</xliff:g> and other open apps detected this screenshot.</string>
<!-- Add to note button used in App Clips flow to return the saved screenshot image to notes app. [CHAR LIMIT=NONE] -->
<string name="app_clips_save_add_to_note">Add to note</string>
- <!-- TODO(b/300307759): Temporary string for text view that displays backlinks data. [CHAR LIMIT=NONE] -->
- <string name="backlinks_string" translatable="false">Open <xliff:g id="appName" example="Google Chrome">%1$s</xliff:g></string>
+ <string name="backlinks_include_link">Include link</string>
<!-- Notification title displayed for screen recording [CHAR LIMIT=50]-->
<string name="screenrecord_title">Screen Recorder</string>
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index c7fde48..52b0b87 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -674,4 +674,11 @@
return factory.create("DeviceEntryIconLog", 100);
}
+ /** Provides a {@link LogBuffer} for use by the volume loggers. */
+ @Provides
+ @SysUISingleton
+ @VolumeLog
+ public static LogBuffer provideVolumeLogBuffer(LogBufferFactory factory) {
+ return factory.create("VolumeLog", 50);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/VolumeLog.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/VolumeLog.kt
new file mode 100644
index 0000000..bc3858a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/VolumeLog.kt
@@ -0,0 +1,22 @@
+/*
+ * 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.log.dagger
+
+import javax.inject.Qualifier
+
+/** A [com.android.systemui.log.LogBuffer] for volume. */
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class VolumeLog
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
index 59b47dc..ab4480d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -29,7 +29,6 @@
import android.app.Activity;
import android.content.BroadcastReceiver;
-import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -46,11 +45,15 @@
import android.util.Log;
import android.view.View;
import android.widget.Button;
+import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.activity.ComponentActivity;
import androidx.annotation.Nullable;
+import androidx.core.graphics.Insets;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
import androidx.lifecycle.ViewModelProvider;
import com.android.internal.logging.UiEventLogger;
@@ -100,7 +103,8 @@
private CropView mCropView;
private Button mSave;
private Button mCancel;
- private TextView mBacklinksData;
+ private CheckBox mBacklinksIncludeDataCheckBox;
+ private TextView mBacklinksDataTextView;
private AppClipsViewModel mViewModel;
private ResultReceiver mResultReceiver;
@@ -156,18 +160,30 @@
mLayout = getLayoutInflater().inflate(R.layout.app_clips_screenshot, null);
mRoot = mLayout.findViewById(R.id.root);
+ // Manually handle window insets post Android V to support edge-to-edge display.
+ ViewCompat.setOnApplyWindowInsetsListener(mRoot, (v, windowInsets) -> {
+ Insets insets = windowInsets.getInsets(
+ WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.displayCutout());
+ v.setPadding(insets.left, insets.top, insets.right, insets.bottom);
+ return WindowInsetsCompat.CONSUMED;
+ });
+
mSave = mLayout.findViewById(R.id.save);
mCancel = mLayout.findViewById(R.id.cancel);
mSave.setOnClickListener(this::onClick);
mCancel.setOnClickListener(this::onClick);
mCropView = mLayout.findViewById(R.id.crop_view);
- mBacklinksData = mLayout.findViewById(R.id.backlinks_data);
mPreview = mLayout.findViewById(R.id.preview);
-
mPreview.addOnLayoutChangeListener(
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) ->
updateImageDimensions());
+ mBacklinksDataTextView = mLayout.findViewById(R.id.backlinks_data);
+ mBacklinksIncludeDataCheckBox = mLayout.findViewById(R.id.backlinks_include_data);
+ mBacklinksIncludeDataCheckBox.setOnCheckedChangeListener(
+ (buttonView, isChecked) ->
+ mBacklinksDataTextView.setVisibility(isChecked ? View.VISIBLE : View.GONE));
+
mViewModel = new ViewModelProvider(this, mViewModelFactory).get(AppClipsViewModel.class);
mViewModel.getScreenshot().observe(this, this::setScreenshot);
mViewModel.getResultLiveData().observe(this, this::setResultThenFinish);
@@ -233,6 +249,9 @@
// Screenshot is now available so set content view.
setContentView(mLayout);
+
+ // Request view to apply insets as it is added late and not when activity was first created.
+ mRoot.requestApplyInsets();
}
private void onClick(View view) {
@@ -284,7 +303,7 @@
mResultReceiver.send(Activity.RESULT_OK, data);
logUiEvent(SCREENSHOT_FOR_NOTE_ACCEPTED);
} catch (Exception e) {
- // Do nothing.
+ Log.e(TAG, "Error while returning data to trampoline activity", e);
}
// Nullify the ResultReceiver before finishing to avoid resending the result.
@@ -297,13 +316,18 @@
finish();
}
- private void setBacklinksData(ClipData clipData) {
- if (mBacklinksData.getVisibility() == View.GONE) {
- mBacklinksData.setVisibility(View.VISIBLE);
- }
+ private void setBacklinksData(InternalBacklinksData backlinksData) {
+ mBacklinksIncludeDataCheckBox.setVisibility(View.VISIBLE);
+ mBacklinksDataTextView.setVisibility(
+ mBacklinksIncludeDataCheckBox.isChecked() ? View.VISIBLE : View.GONE);
- mBacklinksData.setText(String.format(getString(R.string.backlinks_string),
- clipData.getDescription().getLabel()));
+ mBacklinksDataTextView.setText(backlinksData.getClipData().getDescription().getLabel());
+
+ Drawable appIcon = backlinksData.getAppIcon();
+ int size = getResources().getDimensionPixelSize(R.dimen.appclips_backlinks_icon_size);
+ appIcon.setBounds(/* left= */ 0, /* top= */ 0, /* right= */ size, /* bottom= */ size);
+ mBacklinksDataTextView.setCompoundDrawablesRelative(/* start= */ appIcon, /* top= */
+ null, /* end= */ null, /* bottom= */ null);
}
private void setError(int errorCode) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
index 9bb7bbf..d30d518 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsViewModel.java
@@ -92,7 +92,7 @@
private final MutableLiveData<Bitmap> mScreenshotLiveData;
private final MutableLiveData<Uri> mResultLiveData;
private final MutableLiveData<Integer> mErrorLiveData;
- private final MutableLiveData<ClipData> mBacklinksLiveData;
+ private final MutableLiveData<InternalBacklinksData> mBacklinksLiveData;
private AppClipsViewModel(AppClipsCrossProcessHelper appClipsCrossProcessHelper,
ImageExporter imageExporter, IActivityTaskManager atmService,
@@ -144,10 +144,11 @@
*/
void triggerBacklinks(Set<Integer> taskIdsToIgnore, int displayId) {
mBgExecutor.execute(() -> {
- ListenableFuture<ClipData> backlinksData = getBacklinksData(taskIdsToIgnore, displayId);
+ ListenableFuture<InternalBacklinksData> backlinksData = getBacklinksData(
+ taskIdsToIgnore, displayId);
Futures.addCallback(backlinksData, new FutureCallback<>() {
@Override
- public void onSuccess(@Nullable ClipData result) {
+ public void onSuccess(@Nullable InternalBacklinksData result) {
if (result != null) {
mBacklinksLiveData.setValue(result);
}
@@ -180,8 +181,8 @@
return mErrorLiveData;
}
- /** Returns a {@link LiveData} that holds the Backlinks data in {@link ClipData}. */
- LiveData<ClipData> getBacklinksLiveData() {
+ /** Returns a {@link LiveData} that holds Backlinks data in {@link InternalBacklinksData}. */
+ LiveData<InternalBacklinksData> getBacklinksLiveData() {
return mBacklinksLiveData;
}
@@ -226,7 +227,7 @@
return HardwareRenderer.createHardwareBitmap(output, bounds.width(), bounds.height());
}
- private ListenableFuture<ClipData> getBacklinksData(Set<Integer> taskIdsToIgnore,
+ private ListenableFuture<InternalBacklinksData> getBacklinksData(Set<Integer> taskIdsToIgnore,
int displayId) {
return getAllRootTaskInfosOnDisplay(displayId)
.stream()
@@ -264,8 +265,9 @@
!= null;
}
- private ListenableFuture<ClipData> getBacklinksDataForTaskId(RootTaskInfo taskInfo) {
- SettableFuture<ClipData> backlinksData = SettableFuture.create();
+ private ListenableFuture<InternalBacklinksData> getBacklinksDataForTaskId(
+ RootTaskInfo taskInfo) {
+ SettableFuture<InternalBacklinksData> backlinksData = SettableFuture.create();
int taskId = taskInfo.taskId;
mAssistContentRequester.requestAssistContent(taskId, assistContent ->
backlinksData.set(getBacklinksDataFromAssistContent(taskInfo, assistContent)));
@@ -273,7 +275,7 @@
}
/**
- * A utility method to get {@link ClipData} to use for Backlinks functionality from
+ * A utility method to get {@link InternalBacklinksData} to use for Backlinks functionality from
* {@link AssistContent} received from the app whose screenshot is taken.
*
* <p>There are multiple ways an app can provide deep-linkable data via {@link AssistContent}
@@ -289,14 +291,16 @@
*
* @param taskInfo {@link RootTaskInfo} of the task which provided the {@link AssistContent}.
* @param content the {@link AssistContent} to map into Backlinks {@link ClipData}.
- * @return {@link ClipData} that represents the Backlinks data.
+ * @return {@link InternalBacklinksData} that represents the Backlinks data along with app icon.
*/
- private ClipData getBacklinksDataFromAssistContent(RootTaskInfo taskInfo,
+ private InternalBacklinksData getBacklinksDataFromAssistContent(RootTaskInfo taskInfo,
@Nullable AssistContent content) {
String appName = getAppNameOfTask(taskInfo);
String packageName = taskInfo.topActivity.getPackageName();
- ClipData fallback = ClipData.newIntent(appName,
+ Drawable appIcon = taskInfo.topActivityInfo.loadIcon(mPackageManager);
+ ClipData mainLauncherIntent = ClipData.newIntent(appName,
getMainLauncherIntentForPackage(packageName));
+ InternalBacklinksData fallback = new InternalBacklinksData(mainLauncherIntent, appIcon);
if (content == null) {
return fallback;
}
@@ -306,7 +310,7 @@
Uri uri = content.getWebUri();
Intent backlinksIntent = new Intent(ACTION_VIEW).setData(uri);
if (doesIntentResolveToSamePackage(backlinksIntent, packageName)) {
- return ClipData.newRawUri(appName, uri);
+ return new InternalBacklinksData(ClipData.newRawUri(appName, uri), appIcon);
}
}
@@ -314,7 +318,8 @@
if (content.isAppProvidedIntent()) {
Intent backlinksIntent = content.getIntent();
if (doesIntentResolveToSamePackage(backlinksIntent, packageName)) {
- return ClipData.newIntent(appName, backlinksIntent);
+ return new InternalBacklinksData(ClipData.newIntent(appName, backlinksIntent),
+ appIcon);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt
new file mode 100644
index 0000000..0e312f9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/InternalBacklinksData.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.appclips
+
+import android.content.ClipData
+import android.graphics.drawable.Drawable
+
+/** A class to hold the [ClipData] for backlinks and the corresponding app's [Drawable] icon. */
+internal data class InternalBacklinksData(val clipData: ClipData, val appIcon: Drawable)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 96b1cf2..646d0b1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1477,7 +1477,7 @@
}
if (hasRemoteInput) {
result.mView.setWrapper(wrapper);
- result.mView.addOnVisibilityChangedListener(this::setRemoteInputVisible);
+ result.mView.setOnVisibilityChangedListener(this::setRemoteInputVisible);
if (existingPendingIntent != null || result.mView.isActive()) {
// The current action could be gone, or the pending intent no longer valid.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index ddfa86d..715c6e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -1500,6 +1500,7 @@
* needed.
*/
void setOnStackYChanged(Consumer<Boolean> onStackYChanged) {
+ SceneContainerFlag.assertInLegacyMode();
mOnStackYChanged = onStackYChanged;
}
@@ -2270,6 +2271,7 @@
public void setOverscrollTopChangedListener(
OnOverscrollTopChangedListener overscrollTopChangedListener) {
+ SceneContainerFlag.assertInLegacyMode();
mOverscrollTopChangedListener = overscrollTopChangedListener;
}
@@ -5705,6 +5707,7 @@
* Set a listener to when scrolling changes.
*/
public void setOnScrollListener(Consumer<Integer> listener) {
+ SceneContainerFlag.assertInLegacyMode();
mScrollListener = listener;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index bf53ee2..12f8f69 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1052,6 +1052,7 @@
public void setOverscrollTopChangedListener(
OnOverscrollTopChangedListener listener) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setOverscrollTopChangedListener(listener);
}
@@ -1248,6 +1249,7 @@
}
public void setOnStackYChanged(Consumer<Boolean> onStackYChanged) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setOnStackYChanged(onStackYChanged);
}
@@ -1750,6 +1752,7 @@
* Set a listener to when scrolling changes.
*/
public void setOnScrollListener(Consumer<Integer> listener) {
+ SceneContainerFlag.assertInLegacyMode();
mView.setOnScrollListener(listener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index 1fc7bf4..31776cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -115,7 +115,7 @@
private final SendButtonTextWatcher mTextWatcher;
private final TextView.OnEditorActionListener mEditorActionHandler;
private final ArrayList<Runnable> mOnSendListeners = new ArrayList<>();
- private final ArrayList<Consumer<Boolean>> mOnVisibilityChangedListeners = new ArrayList<>();
+ private Consumer<Boolean> mOnVisibilityChangedListener = null;
private final ArrayList<OnFocusChangeListener> mEditTextFocusChangeListeners =
new ArrayList<>();
@@ -733,24 +733,17 @@
* {@link #getVisibility()} would return {@link View#VISIBLE}, and {@code false} it would return
* any other value.
*/
- public void addOnVisibilityChangedListener(Consumer<Boolean> listener) {
- mOnVisibilityChangedListeners.add(listener);
- }
-
- /**
- * Unregister a listener previously registered via
- * {@link #addOnVisibilityChangedListener(Consumer)}.
- */
- public void removeOnVisibilityChangedListener(Consumer<Boolean> listener) {
- mOnVisibilityChangedListeners.remove(listener);
+ public void setOnVisibilityChangedListener(Consumer<Boolean> listener) {
+ mOnVisibilityChangedListener = listener;
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (changedView == this) {
- for (Consumer<Boolean> listener : new ArrayList<>(mOnVisibilityChangedListeners)) {
- listener.accept(visibility == VISIBLE);
+ final Consumer<Boolean> visibilityChangedListener = mOnVisibilityChangedListener;
+ if (visibilityChangedListener != null) {
+ visibilityChangedListener.accept(visibility == VISIBLE);
}
// Hide soft-keyboard when the input view became invisible
// (i.e. The notification shade collapsed by pressing the home key)
diff --git a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
index 2797b8d..e9f4374 100644
--- a/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
+++ b/packages/SystemUI/src/com/android/systemui/stylus/StylusUsiPowerUI.kt
@@ -36,10 +36,10 @@
import com.android.internal.logging.InstanceId
import com.android.internal.logging.InstanceIdSequence
import com.android.internal.logging.UiEventLogger
-import com.android.systemui.res.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.log.DebugLogger.debugLog
+import com.android.systemui.res.R
import com.android.systemui.shared.hardware.hasInputDevice
import com.android.systemui.shared.hardware.isAnyStylusSource
import com.android.systemui.util.NotificationChannels
@@ -65,8 +65,10 @@
private var batteryCapacity = 1.0f
private var suppressed = false
private var instanceId: InstanceId? = null
- @VisibleForTesting var inputDeviceId: Int? = null
- private set
+ @VisibleForTesting
+ var inputDeviceId: Int? = null
+ private set
+
@VisibleForTesting var instanceIdSequence = InstanceIdSequence(1 shl 13)
fun init() {
@@ -113,7 +115,7 @@
inputDeviceId = deviceId
if (batteryState.capacity == batteryCapacity || batteryState.capacity <= 0f)
return@updateBattery
-
+ // Note that batteryState.capacity == NaN will fall through to here
batteryCapacity = batteryState.capacity
debugLog {
"Updating notification battery state to $batteryCapacity " +
@@ -172,7 +174,7 @@
}
private fun isBatteryBelowThreshold(): Boolean {
- return batteryCapacity <= LOW_BATTERY_THRESHOLD
+ return !batteryCapacity.isNaN() && batteryCapacity <= LOW_BATTERY_THRESHOLD
}
private fun hasConnectedBluetoothStylus(): Boolean {
diff --git a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
index 1ae5614..2e7b05a 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/dagger/AudioModule.kt
@@ -32,6 +32,7 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.volume.shared.VolumeLogger
import dagger.Module
import dagger.Provides
import kotlin.coroutines.CoroutineContext
@@ -58,6 +59,7 @@
contentResolver: ContentResolver,
@Background coroutineContext: CoroutineContext,
@Application coroutineScope: CoroutineScope,
+ volumeLogger: VolumeLogger,
): AudioRepository =
AudioRepositoryImpl(
intentsReceiver,
@@ -65,6 +67,7 @@
contentResolver,
coroutineContext,
coroutineScope,
+ volumeLogger,
)
@Provides
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index c18573e..521f608 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -26,6 +26,7 @@
import com.android.settingslib.volume.shared.model.RingerMode
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
+import com.android.systemui.volume.panel.shared.VolumePanelLogger
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
@@ -51,6 +52,7 @@
private val context: Context,
private val audioVolumeInteractor: AudioVolumeInteractor,
private val uiEventLogger: UiEventLogger,
+ private val volumePanelLogger: VolumePanelLogger,
) : SliderViewModel {
private val volumeChanges = MutableStateFlow<Int?>(null)
@@ -105,6 +107,7 @@
audioVolumeInteractor.canChangeVolume(audioStream),
audioVolumeInteractor.ringerMode,
) { model, isEnabled, ringerMode ->
+ volumePanelLogger.onVolumeUpdateReceived(audioStream, model.volume)
model.toState(isEnabled, ringerMode)
}
.stateIn(coroutineScope, SharingStarted.Eagerly, SliderState.Empty)
@@ -112,7 +115,10 @@
init {
volumeChanges
.filterNotNull()
- .onEach { audioVolumeInteractor.setVolume(audioStream, it) }
+ .onEach {
+ volumePanelLogger.onSetVolumeRequested(audioStream, it)
+ audioVolumeInteractor.setVolume(audioStream, it)
+ }
.launchIn(coroutineScope)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt
new file mode 100644
index 0000000..cc513b5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/shared/VolumePanelLogger.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.volume.panel.shared
+
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.VolumeLog
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import javax.inject.Inject
+
+private const val TAG = "SysUI_VolumePanel"
+
+/** Logs events related to the Volume Panel. */
+@VolumePanelScope
+class VolumePanelLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) {
+
+ fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = audioStream.toString()
+ int1 = volume
+ },
+ { "Set volume: stream=$str1 volume=$int1" }
+ )
+ }
+
+ fun onVolumeUpdateReceived(audioStream: AudioStream, volume: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = audioStream.toString()
+ int1 = volume
+ },
+ { "Volume update received: stream=$str1 volume=$int1" }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt b/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt
new file mode 100644
index 0000000..869a82a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/shared/VolumeLogger.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.volume.shared
+
+import com.android.settingslib.volume.data.repository.AudioRepositoryImpl
+import com.android.settingslib.volume.shared.model.AudioStream
+import com.android.settingslib.volume.shared.model.AudioStreamModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.VolumeLog
+import javax.inject.Inject
+
+private const val TAG = "SysUI_Volume"
+
+/** Logs general System UI volume events. */
+@SysUISingleton
+class VolumeLogger @Inject constructor(@VolumeLog private val logBuffer: LogBuffer) :
+ AudioRepositoryImpl.Logger {
+
+ override fun onSetVolumeRequested(audioStream: AudioStream, volume: Int) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = audioStream.toString()
+ int1 = volume
+ },
+ { "Set volume: stream=$str1 volume=$int1" }
+ )
+ }
+
+ override fun onVolumeUpdateReceived(audioStream: AudioStream, model: AudioStreamModel) {
+ logBuffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ {
+ str1 = audioStream.toString()
+ int1 = model.volume
+ },
+ { "Volume update received: stream=$str1 volume=$int1" }
+ )
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
index 2981590..9986205 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -43,6 +43,8 @@
import android.content.pm.PackageManager.ApplicationInfoFlags;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.ShapeDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
@@ -54,6 +56,7 @@
import android.testing.AndroidTestingRunner;
import android.view.Display;
import android.view.View;
+import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.TextView;
@@ -99,11 +102,14 @@
private static final int BACKLINKS_TASK_ID = 42;
private static final String BACKLINKS_TASK_APP_NAME = "Backlinks app";
private static final String BACKLINKS_TASK_PACKAGE_NAME = "backlinksTaskPackageName";
+
private static final RootTaskInfo TASK_THAT_SUPPORTS_BACKLINKS =
createTaskInfoForBacklinksTask();
-
private static final AssistContent ASSIST_CONTENT_FOR_BACKLINKS_TASK =
createAssistContentForBacklinksTask();
+ private static final Drawable FAKE_DRAWABLE = new ShapeDrawable();
+
+ private ArgumentCaptor<Integer> mDisplayIdCaptor = ArgumentCaptor.forClass(Integer.class);
@Mock
private AppClipsCrossProcessHelper mAppClipsCrossProcessHelper;
@@ -171,6 +177,8 @@
assertThat(((ImageView) mActivity.findViewById(R.id.preview)).getDrawable()).isNotNull();
assertThat(mActivity.findViewById(R.id.backlinks_data).getVisibility())
.isEqualTo(View.GONE);
+ assertThat(mActivity.findViewById(R.id.backlinks_include_data).getVisibility())
+ .isEqualTo(View.GONE);
}
@Test
@@ -214,9 +222,44 @@
@Test
@EnableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS)
public void appClipsLaunched_backlinks_displayed() throws RemoteException {
- // Set up mocking to verify backlinks view is displayed on screen.
- ArgumentCaptor<Integer> displayIdCaptor = ArgumentCaptor.forClass(Integer.class);
- when(mAtmService.getAllRootTaskInfosOnDisplay(displayIdCaptor.capture()))
+ setUpMocksForBacklinks();
+
+ launchActivity();
+ waitForIdleSync();
+
+ assertThat(mDisplayIdCaptor.getValue()).isEqualTo(mActivity.getDisplayId());
+ TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
+ assertThat(backlinksData.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(backlinksData.getText().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
+ assertThat(backlinksData.getCompoundDrawablesRelative()[0]).isEqualTo(FAKE_DRAWABLE);
+
+ CheckBox backlinksIncludeData = mActivity.findViewById(R.id.backlinks_include_data);
+ assertThat(backlinksIncludeData.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(backlinksIncludeData.getText().toString())
+ .isEqualTo(mActivity.getString(R.string.backlinks_include_link));
+ assertThat(backlinksIncludeData.isChecked()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_APP_CLIPS_BACKLINKS)
+ public void appClipsLaunched_backlinks_doNotIncludeLink() throws RemoteException {
+ setUpMocksForBacklinks();
+
+ launchActivity();
+ waitForIdleSync();
+ CheckBox backlinksIncludeData = mActivity.findViewById(R.id.backlinks_include_data);
+ runOnMainThread(() -> backlinksIncludeData.performClick());
+ waitForIdleSync();
+
+ assertThat(backlinksIncludeData.getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(backlinksIncludeData.isChecked()).isFalse();
+
+ TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
+ assertThat(backlinksData.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ private void setUpMocksForBacklinks() throws RemoteException {
+ when(mAtmService.getAllRootTaskInfosOnDisplay(mDisplayIdCaptor.capture()))
.thenReturn(List.of(TASK_THAT_SUPPORTS_BACKLINKS));
doAnswer(invocation -> {
AssistContentRequester.Callback callback = invocation.getArgument(1);
@@ -226,15 +269,7 @@
when(mPackageManager
.resolveActivity(any(Intent.class), anyInt()))
.thenReturn(createBacklinksTaskResolveInfo());
-
- launchActivity();
- waitForIdleSync();
-
- assertThat(displayIdCaptor.getValue()).isEqualTo(mActivity.getDisplayId());
- TextView backlinksData = mActivity.findViewById(R.id.backlinks_data);
- assertThat(backlinksData.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(backlinksData.getText().toString()).isEqualTo(
- mActivity.getString(R.string.backlinks_string, BACKLINKS_TASK_APP_NAME));
+ when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
}
private void launchActivity() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
index dcb75d1..baf1357 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsViewModelTest.java
@@ -111,6 +111,7 @@
.thenReturn(List.of(createTaskInfoForBacklinksTask()));
when(mPackageManager.resolveActivity(mPackageManagerIntentCaptor.capture(), anyInt()))
.thenReturn(createBacklinksTaskResolveInfo());
+ when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
mViewModel = new AppClipsViewModel.Factory(mAppClipsCrossProcessHelper, mImageExporter,
mAtmService, mAssistContentRequester, mPackageManager,
@@ -202,12 +203,14 @@
assertThat(queriedIntent.getData()).isEqualTo(expectedUri);
assertThat(queriedIntent.getAction()).isEqualTo(ACTION_VIEW);
- ClipData result = mViewModel.getBacklinksLiveData().getValue();
- ClipDescription resultDescription = result.getDescription();
+ InternalBacklinksData result = mViewModel.getBacklinksLiveData().getValue();
+ assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
+ ClipData clipData = result.getClipData();
+ ClipDescription resultDescription = clipData.getDescription();
assertThat(resultDescription.getLabel().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
assertThat(resultDescription.getMimeType(0)).isEqualTo(MIMETYPE_TEXT_URILIST);
- assertThat(result.getItemCount()).isEqualTo(1);
- assertThat(result.getItemAt(0).getUri()).isEqualTo(expectedUri);
+ assertThat(clipData.getItemCount()).isEqualTo(1);
+ assertThat(clipData.getItemAt(0).getUri()).isEqualTo(expectedUri);
}
@Test
@@ -245,12 +248,14 @@
Intent queriedIntent = mPackageManagerIntentCaptor.getValue();
assertThat(queriedIntent.getPackage()).isEqualTo(expectedIntent.getPackage());
- ClipData result = mViewModel.getBacklinksLiveData().getValue();
- ClipDescription resultDescription = result.getDescription();
+ InternalBacklinksData result = mViewModel.getBacklinksLiveData().getValue();
+ assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
+ ClipData clipData = result.getClipData();
+ ClipDescription resultDescription = clipData.getDescription();
assertThat(resultDescription.getLabel().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
assertThat(resultDescription.getMimeType(0)).isEqualTo(MIMETYPE_TEXT_INTENT);
- assertThat(result.getItemCount()).isEqualTo(1);
- assertThat(result.getItemAt(0).getIntent()).isEqualTo(expectedIntent);
+ assertThat(clipData.getItemCount()).isEqualTo(1);
+ assertThat(clipData.getItemAt(0).getIntent()).isEqualTo(expectedIntent);
}
@Test
@@ -330,6 +335,7 @@
private void resetPackageManagerMockingForUsingFallbackBacklinks() {
reset(mPackageManager);
+ when(mPackageManager.loadItemIcon(any(), any())).thenReturn(FAKE_DRAWABLE);
when(mPackageManager.resolveActivity(any(Intent.class), anyInt()))
// First the logic queries whether a package has a launcher activity, this should
// resolve otherwise the logic filters out the task.
@@ -340,14 +346,17 @@
}
private void verifyMainLauncherBacklinksIntent() {
- ClipData result = mViewModel.getBacklinksLiveData().getValue();
- assertThat(result.getItemCount()).isEqualTo(1);
+ InternalBacklinksData result = mViewModel.getBacklinksLiveData().getValue();
+ assertThat(result.getAppIcon()).isEqualTo(FAKE_DRAWABLE);
- ClipDescription resultDescription = result.getDescription();
+ ClipData clipData = result.getClipData();
+ assertThat(clipData.getItemCount()).isEqualTo(1);
+
+ ClipDescription resultDescription = clipData.getDescription();
assertThat(resultDescription.getLabel().toString()).isEqualTo(BACKLINKS_TASK_APP_NAME);
assertThat(resultDescription.getMimeType(0)).isEqualTo(MIMETYPE_TEXT_INTENT);
- Intent actualBacklinksIntent = result.getItemAt(0).getIntent();
+ Intent actualBacklinksIntent = clipData.getItemAt(0).getIntent();
assertThat(actualBacklinksIntent.getPackage()).isEqualTo(BACKLINKS_TASK_PACKAGE_NAME);
assertThat(actualBacklinksIntent.getAction()).isEqualTo(ACTION_MAIN);
assertThat(actualBacklinksIntent.getCategories()).containsExactly(CATEGORY_LAUNCHER);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 70afbd8..ffe7750 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -247,7 +247,7 @@
ExpandableNotificationRow row = helper.createRow();
RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
- view.addOnVisibilityChangedListener(null);
+ view.setOnVisibilityChangedListener(null);
view.setVisibility(View.INVISIBLE);
view.setVisibility(View.VISIBLE);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
index 5b9db4b..5603ff0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/stylus/StylusUsiPowerUiTest.kt
@@ -31,8 +31,8 @@
import com.android.internal.logging.InstanceId
import com.android.internal.logging.UiEventLogger
import com.android.systemui.InstanceIdSequenceFake
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
@@ -109,6 +109,14 @@
}
@Test
+ fun updateBatteryState_capacityNaN_cancelsNotification() {
+ stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(Float.NaN))
+
+ verify(notificationManager, times(1)).cancel(R.string.stylus_battery_low_percentage)
+ verifyNoMoreInteractions(notificationManager)
+ }
+
+ @Test
fun updateBatteryState_capacityBelowThreshold_notifies() {
stylusUsiPowerUi.updateBatteryState(0, FixedCapacityBatteryState(0.1f))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
index 97688d5..ef2d4ce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProviderTest.kt
@@ -29,10 +29,16 @@
import com.android.systemui.unfold.util.TestFoldStateProvider
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+/**
+ * This test class tests [PhysicsBasedUnfoldTransitionProgressProvider] in a more E2E
+ * fashion, it uses real handler thread and timings, so it might be perceptible to more flakiness
+ * compared to the other unit tests that do not perform real multithreaded interactions.
+ */
@RunWith(AndroidJUnit4::class)
@SmallTest
class PhysicsBasedUnfoldTransitionProgressProviderTest : SysuiTestCase() {
@@ -44,8 +50,8 @@
mock<UnfoldFrameCallbackScheduler.Factory>().apply {
whenever(create()).then { UnfoldFrameCallbackScheduler() }
}
- private val mockBgHandler = mock<Handler>()
- private val fakeHandler = Handler(HandlerThread("UnfoldBg").apply { start() }.looper)
+ private val handlerThread = HandlerThread("UnfoldBg").apply { start() }
+ private val bgHandler = Handler(handlerThread.looper)
@Before
fun setUp() {
@@ -54,20 +60,26 @@
context,
schedulerFactory,
foldStateProvider = foldStateProvider,
- progressHandler = fakeHandler
+ progressHandler = bgHandler
)
progressProvider.addCallback(listener)
}
+ @After
+ fun after() {
+ handlerThread.quit()
+ }
+
@Test
fun testUnfold_emitsIncreasingTransitionEvents() {
runOnProgressThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
{ foldStateProvider.sendHingeAngleUpdate(10f) },
- { foldStateProvider.sendUnfoldedScreenAvailable() },
- { foldStateProvider.sendHingeAngleUpdate(90f) },
- { foldStateProvider.sendHingeAngleUpdate(180f) },
- { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) },
+ { foldStateProvider.sendUnfoldedScreenAvailable() }
+ )
+ sendHingeAngleAndEnsureAnimationUpdate(90f, 120f, 180f)
+ runOnProgressThreadWithInterval(
+ { foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_FULL_OPEN) }
)
with(listener.ensureTransitionFinished()) {
@@ -91,7 +103,7 @@
}
@Test
- fun testUnfold_screenAvailableOnlyAfterFullUnfold_emitsIncreasingTransitionEvents() {
+ fun testUnfold_screenAvailableOnlyAfterFullUnfold_finishesWithUnfoldEvent() {
runOnProgressThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
{ foldStateProvider.sendHingeAngleUpdate(10f) },
@@ -102,7 +114,6 @@
)
with(listener.ensureTransitionFinished()) {
- assertIncreasingProgress()
assertFinishedWithUnfold()
}
}
@@ -111,9 +122,9 @@
fun testFold_emitsDecreasingTransitionEvents() {
runOnProgressThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_CLOSING) },
- { foldStateProvider.sendHingeAngleUpdate(170f) },
- { foldStateProvider.sendHingeAngleUpdate(90f) },
- { foldStateProvider.sendHingeAngleUpdate(10f) },
+ )
+ sendHingeAngleAndEnsureAnimationUpdate(170f, 90f, 10f)
+ runOnProgressThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_CLOSED) },
)
@@ -127,9 +138,9 @@
fun testUnfoldAndStopUnfolding_finishesTheUnfoldTransition() {
runOnProgressThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_START_OPENING) },
- { foldStateProvider.sendUnfoldedScreenAvailable() },
- { foldStateProvider.sendHingeAngleUpdate(10f) },
- { foldStateProvider.sendHingeAngleUpdate(90f) },
+ { foldStateProvider.sendUnfoldedScreenAvailable() })
+ sendHingeAngleAndEnsureAnimationUpdate(10f, 50f, 90f)
+ runOnProgressThreadWithInterval(
{ foldStateProvider.sendFoldUpdate(FOLD_UPDATE_FINISH_HALF_OPEN) },
)
@@ -159,12 +170,22 @@
with(listener.ensureTransitionFinished()) { assertHasFoldAnimationAtTheEnd() }
}
+ private fun sendHingeAngleAndEnsureAnimationUpdate(vararg angles: Float) {
+ angles.forEach { angle ->
+ listener.waitForProgressChangeAfter {
+ bgHandler.post {
+ foldStateProvider.sendHingeAngleUpdate(angle)
+ }
+ }
+ }
+ }
+
private fun runOnProgressThreadWithInterval(
vararg blocks: () -> Unit,
intervalMillis: Long = 60,
) {
blocks.forEach {
- fakeHandler.post(it)
+ bgHandler.post(it)
Thread.sleep(intervalMillis)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
index bbc96f70..6e8bf85 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/progress/TestUnfoldProgressListener.kt
@@ -68,6 +68,24 @@
return recordings.first()
}
+ /**
+ * Number of progress event for the currently running transition
+ * Returns null if there is no currently running transition
+ */
+ val currentTransitionProgressEventCount: Int?
+ get() = currentRecording?.progressHistory?.size
+
+ /**
+ * Runs [block] and ensures that there was at least once onTransitionProgress event after that
+ */
+ fun waitForProgressChangeAfter(block: () -> Unit) {
+ val eventCount = currentTransitionProgressEventCount
+ block()
+ waitForCondition {
+ currentTransitionProgressEventCount != eventCount
+ }
+ }
+
fun assertStarted() {
assertWithMessage("Transition didn't start").that(currentRecording).isNotNull()
}
@@ -86,7 +104,7 @@
}
class UnfoldTransitionRecording {
- private val progressHistory: MutableList<Float> = arrayListOf()
+ val progressHistory: MutableList<Float> = arrayListOf()
private var finishingInvocations: Int = 0
fun addProgress(progress: Float) {
@@ -142,6 +160,6 @@
}
private companion object {
- private const val MIN_ANIMATION_EVENTS = 5
+ private const val MIN_ANIMATION_EVENTS = 3
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
index b2b19de..e6b52f0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModelKosmos.kt
@@ -20,6 +20,7 @@
import com.android.internal.logging.uiEventLogger
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.volume.domain.interactor.audioVolumeInteractor
+import com.android.systemui.volume.shared.volumePanelLogger
import kotlinx.coroutines.CoroutineScope
val Kosmos.audioStreamSliderViewModelFactory by
@@ -36,6 +37,7 @@
applicationContext,
audioVolumeInteractor,
uiEventLogger,
+ volumePanelLogger,
)
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/shared/VolumePanelLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/shared/VolumePanelLoggerKosmos.kt
new file mode 100644
index 0000000..3a7574d
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/shared/VolumePanelLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * 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.volume.shared
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.volume.panel.shared.VolumePanelLogger
+
+val Kosmos.volumePanelLogger by Kosmos.Fixture { VolumePanelLogger(logcatLogBuffer()) }
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 4c8febf..e2eb09f 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -274,12 +274,14 @@
src: "scripts/ravenwood-stats-checker.sh",
test_suites: ["general-tests"],
data: [
- ":framework-minus-apex.ravenwood.stats",
- ":framework-minus-apex.ravenwood.apis",
- ":framework-minus-apex.ravenwood.keep_all",
- ":services.core.ravenwood.stats",
- ":services.core.ravenwood.apis",
- ":services.core.ravenwood.keep_all",
+ ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_stats.csv}",
+ ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_apis.csv}",
+ ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_keep_all.txt}",
+ ":framework-minus-apex.ravenwood-base{hoststubgen_framework-minus-apex_dump.txt}",
+ ":services.core.ravenwood-base{hoststubgen_services.core_stats.csv}",
+ ":services.core.ravenwood-base{hoststubgen_services.core_apis.csv}",
+ ":services.core.ravenwood-base{hoststubgen_services.core_keep_all.txt}",
+ ":services.core.ravenwood-base{hoststubgen_services.core_dump.txt}",
],
}
diff --git a/ravenwood/scripts/ravenwood-stats-collector.sh b/ravenwood/scripts/ravenwood-stats-collector.sh
index 43b61a4..36601bd 100755
--- a/ravenwood/scripts/ravenwood-stats-collector.sh
+++ b/ravenwood/scripts/ravenwood-stats-collector.sh
@@ -22,10 +22,12 @@
stats=$out_dir/ravenwood-stats-all.csv
apis=$out_dir/ravenwood-apis-all.csv
keep_all_dir=$out_dir/ravenwood-keep-all/
+dump_dir=$out_dir/ravenwood-dump/
rm -fr $out_dir
mkdir -p $out_dir
mkdir -p $keep_all_dir
+mkdir -p $dump_dir
# Where the input files are.
path=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/ravenwood-stats-checker/x86_64/
@@ -85,4 +87,8 @@
cp *keep_all.txt $keep_all_dir
echo "Keep all files created at:"
-find $keep_all_dir -type f
\ No newline at end of file
+find $keep_all_dir -type f
+
+cp *dump.txt $dump_dir
+echo "Dump files created at:"
+find $dump_dir -type f
\ No newline at end of file
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 04c4284..a72259e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -185,8 +185,8 @@
private final SparseIntArray mDevicePolicies;
@GuardedBy("mVirtualDeviceLock")
private final SparseArray<VirtualDisplayWrapper> mVirtualDisplays = new SparseArray<>();
- private final IVirtualDeviceActivityListener mActivityListener;
- private final IVirtualDeviceSoundEffectListener mSoundEffectListener;
+ private IVirtualDeviceActivityListener mActivityListener;
+ private IVirtualDeviceSoundEffectListener mSoundEffectListener;
private final DisplayManagerGlobal mDisplayManager;
private final DisplayManagerInternal mDisplayManagerInternal;
@GuardedBy("mVirtualDeviceLock")
@@ -303,7 +303,9 @@
UserHandle ownerUserHandle = UserHandle.getUserHandleForUid(attributionSource.getUid());
mContext = context.createContextAsUser(ownerUserHandle, 0);
mAssociationInfo = associationInfo;
- mPersistentDeviceId = createPersistentDeviceId(associationInfo.getId());
+ mPersistentDeviceId = associationInfo == null
+ ? null
+ : createPersistentDeviceId(associationInfo.getId());
mService = service;
mPendingTrampolineCallback = pendingTrampolineCallback;
mActivityListener = activityListener;
@@ -405,7 +407,7 @@
/** Returns the device display name. */
CharSequence getDisplayName() {
- return mAssociationInfo.getDisplayName();
+ return mAssociationInfo == null ? mParams.getName() : mAssociationInfo.getDisplayName();
}
/** Returns the public representation of the device. */
@@ -420,6 +422,22 @@
}
}
+ /**
+ * Setter for listeners that live in the client process, namely in
+ * {@link android.companion.virtual.VirtualDeviceInternal}.
+ *
+ * This is needed for virtual devices that are created by the system, as the VirtualDeviceImpl
+ * object is created before the returned VirtualDeviceInternal one.
+ */
+ @Override // Binder call
+ @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+ public void setListeners(@NonNull IVirtualDeviceActivityListener activityListener,
+ @NonNull IVirtualDeviceSoundEffectListener soundEffectListener) {
+ super.setListeners_enforcePermission();
+ mActivityListener = Objects.requireNonNull(activityListener);
+ mSoundEffectListener = Objects.requireNonNull(soundEffectListener);
+ }
+
@Override // Binder call
public @VirtualDeviceParams.DevicePolicy int getDevicePolicy(
@VirtualDeviceParams.PolicyType int policyType) {
@@ -456,7 +474,9 @@
@Override // Binder call
public int getAssociationId() {
- return mAssociationInfo.getId();
+ return mAssociationInfo == null
+ ? VirtualDeviceManagerService.CDM_ASSOCIATION_ID_NONE
+ : mAssociationInfo.getId();
}
@Override // Binder call
@@ -1140,7 +1160,7 @@
String indent = " ";
fout.println(" VirtualDevice: ");
fout.println(indent + "mDeviceId: " + mDeviceId);
- fout.println(indent + "mAssociationId: " + mAssociationInfo.getId());
+ fout.println(indent + "mAssociationId: " + getAssociationId());
fout.println(indent + "mOwnerPackageName: " + mOwnerPackageName);
fout.println(indent + "mParams: ");
mParams.dump(fout, indent + indent);
@@ -1286,8 +1306,7 @@
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
private void onActivityBlocked(int displayId, ActivityInfo activityInfo) {
- Intent intent = BlockedAppStreamingActivity.createIntent(
- activityInfo, mAssociationInfo.getDisplayName());
+ Intent intent = BlockedAppStreamingActivity.createIntent(activityInfo, getDisplayName());
mContext.startActivityAsUser(
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
@@ -1374,7 +1393,7 @@
@SuppressWarnings("AndroidFrameworkRequiresPermission")
private void checkVirtualInputDeviceDisplayIdAssociation(int displayId) {
- if (mContext.checkCallingPermission(android.Manifest.permission.INJECT_EVENTS)
+ if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.INJECT_EVENTS)
== PackageManager.PERMISSION_GRANTED) {
// The INJECT_EVENTS permission allows for injecting input to any window / display.
return;
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java
index c65aa5b..b0bacfd 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceLog.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Binder;
+import android.os.Process;
import android.util.SparseArray;
import java.io.PrintWriter;
@@ -35,6 +36,8 @@
"MM-dd HH:mm:ss.SSS").withZone(ZoneId.systemDefault());
private static final int MAX_ENTRIES = 16;
+ private static final String VIRTUAL_DEVICE_OWNER_SYSTEM = "system";
+
private final Context mContext;
private final ArrayDeque<LogEntry> mLogEntries = new ArrayDeque<>();
@@ -132,6 +135,8 @@
String[] packages;
if (mUidToPackagesCache.contains(ownerUid)) {
return mUidToPackagesCache.get(ownerUid);
+ } else if (ownerUid == Process.SYSTEM_UID) {
+ return VIRTUAL_DEVICE_OWNER_SYSTEM;
} else {
packages = mPackageManager.getPackagesForUid(ownerUid);
String packageName = "";
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 9ad73ca..1be1d2b 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -101,6 +101,11 @@
AssociationRequest.DEVICE_PROFILE_APP_STREAMING,
AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING);
+ /**
+ * A virtual device association id corresponding to no CDM association.
+ */
+ static final int CDM_ASSOCIATION_ID_NONE = 0;
+
private final Object mVirtualDeviceManagerLock = new Object();
private final VirtualDeviceManagerImpl mImpl;
private final VirtualDeviceManagerNativeImpl mNativeImpl;
@@ -316,7 +321,9 @@
for (int i = 0; i < mVirtualDevices.size(); i++) {
VirtualDeviceImpl virtualDevice = mVirtualDevices.valueAt(i);
- if (!activeAssociationIds.contains(virtualDevice.getAssociationId())) {
+ int deviceAssociationId = virtualDevice.getAssociationId();
+ if (deviceAssociationId != CDM_ASSOCIATION_ID_NONE
+ && !activeAssociationIds.contains(deviceAssociationId)) {
virtualDevicesToRemove.add(virtualDevice);
}
}
@@ -422,28 +429,39 @@
@NonNull IVirtualDeviceActivityListener activityListener,
@NonNull IVirtualDeviceSoundEffectListener soundEffectListener) {
createVirtualDevice_enforcePermission();
- attributionSource.enforceCallingUid();
-
- final int callingUid = getCallingUid();
+ Objects.requireNonNull(activityListener);
+ Objects.requireNonNull(soundEffectListener);
final String packageName = attributionSource.getPackageName();
- if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) {
- throw new SecurityException(
- "Package name " + packageName + " does not belong to calling uid "
- + callingUid);
- }
AssociationInfo associationInfo = getAssociationInfo(packageName, associationId);
if (associationInfo == null) {
throw new IllegalArgumentException("No association with ID " + associationId);
- }
- if (!VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES
+ } else if (!VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES
.contains(associationInfo.getDeviceProfile())
&& Flags.persistentDeviceIdApi()) {
throw new IllegalArgumentException("Unsupported CDM Association device profile "
+ associationInfo.getDeviceProfile() + " for virtual device creation.");
}
+ return createVirtualDevice(token, attributionSource, associationInfo, params,
+ activityListener, soundEffectListener);
+ }
+
+ private IVirtualDevice createVirtualDevice(
+ IBinder token,
+ AttributionSource attributionSource,
+ AssociationInfo associationInfo,
+ @NonNull VirtualDeviceParams params,
+ @Nullable IVirtualDeviceActivityListener activityListener,
+ @Nullable IVirtualDeviceSoundEffectListener soundEffectListener) {
+ createVirtualDevice_enforcePermission();
+ attributionSource.enforceCallingUid();
+
+ final String packageName = attributionSource.getPackageName();
+ if (!PermissionUtils.validateCallingPackageName(getContext(), packageName)) {
+ throw new SecurityException(
+ "Package name " + packageName + " does not belong to calling uid "
+ + getCallingUid());
+ }
Objects.requireNonNull(params);
- Objects.requireNonNull(activityListener);
- Objects.requireNonNull(soundEffectListener);
final UserHandle userHandle = getCallingUserHandle();
final CameraAccessController cameraAccessController =
@@ -724,6 +742,21 @@
private final ArraySet<Integer> mAllUidsOnVirtualDevice = new ArraySet<>();
@Override
+ public @NonNull VirtualDeviceManager.VirtualDevice createVirtualDevice(
+ @NonNull VirtualDeviceParams params) {
+ Objects.requireNonNull(params, "params must not be null");
+ Objects.requireNonNull(params.getName(), "virtual device name must not be null");
+ IVirtualDevice virtualDevice = mImpl.createVirtualDevice(
+ new Binder(),
+ getContext().getAttributionSource(),
+ /* associationInfo= */ null,
+ params,
+ /* activityListener= */ null,
+ /* soundEffectListener= */ null);
+ return new VirtualDeviceManager.VirtualDevice(mImpl, getContext(), virtualDevice);
+ }
+
+ @Override
public int getDeviceOwnerUid(int deviceId) {
VirtualDeviceImpl virtualDevice;
synchronized (mVirtualDeviceManagerLock) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 27fda15..a8c269d 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2793,12 +2793,13 @@
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 ba7aee0..6ff4a61 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 @@
* 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 @@
});
checkDeviceInventorySize_l();
}
- mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
+ if (syncInventory) {
+ mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState);
+ }
}
/**
@@ -196,7 +199,8 @@
* 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 @@
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 @@
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 @@
}
/**
- * 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 @@
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 @@
// 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 c89992d..dcce96b 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9639,6 +9639,9 @@
case MSG_INIT_SPATIALIZER:
onInitSpatializer();
+ // the device inventory can only be synchronized after the
+ // spatializer has been initialized
+ mDeviceBroker.postSynchronizeAdiDevicesInInventory(null);
mAudioEventWakeLock.release();
break;
@@ -11394,7 +11397,8 @@
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 cae1695..9265ff2 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 @@
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 @@
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/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
index b179783..6e38733 100644
--- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
+++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java
@@ -20,6 +20,8 @@
import android.annotation.Nullable;
import android.companion.virtual.IVirtualDevice;
import android.companion.virtual.VirtualDevice;
+import android.companion.virtual.VirtualDeviceManager;
+import android.companion.virtual.VirtualDeviceParams;
import android.companion.virtual.sensor.VirtualSensor;
import android.content.Context;
import android.os.LocaleList;
@@ -180,4 +182,14 @@
* exists, as long as one may have existed or can be created.
*/
public abstract @NonNull Set<String> getAllPersistentDeviceIds();
+
+ /**
+ * Creates a virtual device where applications can launch and receive input events injected by
+ * the creator.
+ *
+ * <p>A Companion Device Manager association is not required. Only the system may create such
+ * virtual devices.</p>
+ */
+ public abstract @NonNull VirtualDeviceManager.VirtualDevice createVirtualDevice(
+ @NonNull VirtualDeviceParams params);
}
diff --git a/services/core/java/com/android/server/crashrecovery/TEST_MAPPING b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
new file mode 100644
index 0000000..4a66bac
--- /dev/null
+++ b/services/core/java/com/android/server/crashrecovery/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "postsubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.RescuePartyTest"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecController.java b/services/core/java/com/android/server/hdmi/HdmiCecController.java
index cd2c037..5696fba 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecController.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecController.java
@@ -427,7 +427,7 @@
@ServiceThreadOnly
void setHpdSignalType(@Constants.HpdSignalType int signal, int portId) {
assertRunOnServiceThread();
- HdmiLogger.debug("setHpdSignalType: portId %b, signal %b", portId, signal);
+ HdmiLogger.debug("setHpdSignalType: portId %d, signal %d", portId, signal);
mNativeWrapperImpl.nativeSetHpdSignalType(signal, portId);
}
@@ -439,7 +439,7 @@
@Constants.HpdSignalType
int getHpdSignalType(int portId) {
assertRunOnServiceThread();
- HdmiLogger.debug("getHpdSignalType: portId %b ", portId);
+ HdmiLogger.debug("getHpdSignalType: portId %d ", portId);
return mNativeWrapperImpl.nativeGetHpdSignalType(portId);
}
diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
index ec95298..58b14b1 100644
--- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
+++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java
@@ -28,6 +28,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.UserInfo;
import android.media.AudioFocusInfo;
import android.media.AudioManager;
import android.media.AudioPlaybackConfiguration;
@@ -183,8 +184,8 @@
foregroundContext) throws RemoteException {
final int userId = UserHandle.getUserId(afi.getClientUid());
final int usage = afi.getAttributes().getUsage();
- String userName = mUserManager.getUserInfo(userId).name;
- if (userId != foregroundContext.getUserId()) {
+ UserInfo userInfo = mUserManager.getUserInfo(userId);
+ if (userInfo != null && userId != foregroundContext.getUserId()) {
//TODO: b/349138482 - Add handling of cases when usage == USAGE_NOTIFICATION_RINGTONE
if (usage == USAGE_ALARM) {
Intent muteIntent = createIntent(ACTION_MUTE_SOUND, afi, foregroundContext, userId);
@@ -199,7 +200,7 @@
mUserWithNotification = foregroundContext.getUserId();
mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(),
- createNotification(userName, mutePI, switchPI, foregroundContext),
+ createNotification(userInfo.name, mutePI, switchPI, foregroundContext),
foregroundContext.getUser());
}
}
diff --git a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
index 34c90f1..edd2fa9 100644
--- a/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
+++ b/services/core/java/com/android/server/tv/interactive/TvInteractiveAppManagerService.java
@@ -2124,7 +2124,7 @@
@Override
public void setTeletextAppEnabled(IBinder sessionToken, boolean enable, int userId) {
if (DEBUG) {
- Slogf.d(TAG, "setTeletextAppEnabled(enable=%d)", enable);
+ Slogf.d(TAG, "setTeletextAppEnabled(enable=%b)", enable);
}
final int callingUid = Binder.getCallingUid();
final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 9d1551c..b7f8505 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.util.SparseArray;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.window.flags.Flags;
import java.util.function.Consumer;
@@ -80,6 +81,20 @@
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/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 72ec058..5215609 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -3796,7 +3796,7 @@
hideBootMessagesLocked();
// If the screen still doesn't come up after 30 seconds, give
// up and turn it on.
- mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30 * 1000);
+ mH.sendEmptyMessageDelayed(H.BOOT_TIMEOUT, 30 * 1000 * Build.HW_TIMEOUT_MULTIPLIER);
}
mPolicy.systemBooted();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index f72e82a..d845968 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3695,7 +3695,7 @@
void fillInsetsSourceControls(@NonNull InsetsSourceControl.Array outArray,
boolean copyControls) {
- final int lastSeq = mLastReportedInsetsState.getSeq();
+ final int lastSeq = mLastReportedActiveControls.getSeq();
final InsetsSourceControl[] controls =
getDisplayContent().getInsetsStateController().getControlsForDispatch(this);
outArray.set(controls, copyControls);
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 6fd7aa0..9ecd492 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 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 @@
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 @@
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/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 031dd5b..9b8c3b3 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -836,6 +836,28 @@
return 1;
}
+ // Parse the feature flag values. An argument that starts with '@' points to a file to read flag
+ // values from.
+ std::vector<std::string> all_feature_flags_args;
+ for (const std::string& arg : feature_flags_args_) {
+ if (util::StartsWith(arg, "@")) {
+ const std::string path = arg.substr(1, arg.size() - 1);
+ std::string error;
+ if (!file::AppendArgsFromFile(path, &all_feature_flags_args, &error)) {
+ context.GetDiagnostics()->Error(android::DiagMessage(path) << error);
+ return 1;
+ }
+ } else {
+ all_feature_flags_args.push_back(arg);
+ }
+ }
+
+ for (const std::string& arg : all_feature_flags_args) {
+ if (!ParseFeatureFlagsParameter(arg, context.GetDiagnostics(), &options_.feature_flag_values)) {
+ return 1;
+ }
+ }
+
return Compile(&context, file_collection.get(), archive_writer.get(), options_);
}
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index 61c5b60..70c8791 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -24,6 +24,7 @@
#include "Command.h"
#include "ResourceTable.h"
#include "androidfw/IDiagnostics.h"
+#include "cmd/Util.h"
#include "format/Archive.h"
#include "process/IResourceTableConsumer.h"
@@ -45,6 +46,7 @@
bool preserve_visibility_of_styleables = false;
bool verbose = false;
std::optional<std::string> product_;
+ FeatureFlagValues feature_flag_values;
};
/** Parses flags and compiles resources to be used in linking. */
@@ -92,6 +94,12 @@
"Leave only resources specific to the given product. All "
"other resources (including defaults) are removed.",
&options_.product_);
+ AddOptionalFlagList("--feature-flags",
+ "Specify the values of feature flags. The pairs in the argument\n"
+ "are separated by ',' the name is separated from the value by '='.\n"
+ "The name can have a suffix of ':ro' to indicate it is read only."
+ "Example: \"flag1=true,flag2:ro=false,flag3=\" (flag3 has no given value).",
+ &feature_flags_args_);
}
int Action(const std::vector<std::string>& args) override;
@@ -101,6 +109,7 @@
CompileOptions options_;
std::optional<std::string> visibility_;
std::optional<std::string> trace_folder_;
+ std::vector<std::string> feature_flags_args_;
};
int Compile(IAaptContext* context, io::IFileCollection* inputs, IArchiveWriter* output_writer,
diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h
index 8fe414f..2f17853 100644
--- a/tools/aapt2/cmd/Link.h
+++ b/tools/aapt2/cmd/Link.h
@@ -332,8 +332,9 @@
AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_);
AddOptionalFlagList("--feature-flags",
"Specify the values of feature flags. The pairs in the argument\n"
- "are separated by ',' and the name is separated from the value by '='.\n"
- "Example: \"flag1=true,flag2=false,flag3=\" (flag3 has no given value).",
+ "are separated by ',' the name is separated from the value by '='.\n"
+ "The name can have a suffix of ':ro' to indicate it is read only."
+ "Example: \"flag1=true,flag2:ro=false,flag3=\" (flag3 has no given value).",
&feature_flags_args_);
AddOptionalSwitch("--non-updatable-system",
"Mark the app as a non-updatable system app. This inserts\n"
diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp
index 678d846..e839fc1 100644
--- a/tools/aapt2/cmd/Util.cpp
+++ b/tools/aapt2/cmd/Util.cpp
@@ -128,7 +128,7 @@
if (parts.size() > 2) {
diag->Error(android::DiagMessage()
<< "Invalid feature flag and optional value '" << flag_and_value
- << "'. Must be in the format 'flag_name[=true|false]");
+ << "'. Must be in the format 'flag_name[:ro][=true|false]");
return false;
}
@@ -137,6 +137,25 @@
diag->Error(android::DiagMessage() << "No name given for one or more flags in: " << arg);
return false;
}
+ std::vector<std::string> name_parts = util::Split(flag_name, ':');
+ if (name_parts.size() > 2) {
+ diag->Error(android::DiagMessage()
+ << "Invalid feature flag and optional value '" << flag_and_value
+ << "'. Must be in the format 'flag_name[:ro][=true|false]");
+ return false;
+ }
+ flag_name = name_parts[0];
+ bool read_only = false;
+ if (name_parts.size() == 2) {
+ if (name_parts[1] == "ro") {
+ read_only = true;
+ } else {
+ diag->Error(android::DiagMessage()
+ << "Invalid feature flag and optional value '" << flag_and_value
+ << "'. Must be in the format 'flag_name[:ro][=true|false]");
+ return false;
+ }
+ }
std::optional<bool> flag_value = {};
if (parts.size() == 2) {
@@ -151,13 +170,13 @@
}
}
- if (auto [it, inserted] =
- out_feature_flag_values->try_emplace(std::string(flag_name), flag_value);
+ auto ffp = FeatureFlagProperties{read_only, flag_value};
+ if (auto [it, inserted] = out_feature_flag_values->try_emplace(std::string(flag_name), ffp);
!inserted) {
// We are allowing the same flag to appear multiple times, last value wins.
diag->Note(android::DiagMessage()
<< "Value for feature flag '" << flag_name << "' was given more than once");
- it->second = flag_value;
+ it->second = ffp;
}
}
return true;
diff --git a/tools/aapt2/cmd/Util.h b/tools/aapt2/cmd/Util.h
index 9ece5dd..6b8813b 100644
--- a/tools/aapt2/cmd/Util.h
+++ b/tools/aapt2/cmd/Util.h
@@ -37,7 +37,17 @@
namespace aapt {
-using FeatureFlagValues = std::map<std::string, std::optional<bool>, std::less<>>;
+struct FeatureFlagProperties {
+ bool read_only;
+ std::optional<bool> enabled;
+
+ FeatureFlagProperties(bool ro, std::optional<bool> e) : read_only(ro), enabled(e) {
+ }
+
+ bool operator==(const FeatureFlagProperties&) const = default;
+};
+
+using FeatureFlagValues = std::map<std::string, FeatureFlagProperties, std::less<>>;
// Parses a configuration density (ex. hdpi, xxhdpi, 234dpi, anydpi, etc).
// Returns Nothing and logs a human friendly error message if the string was not legal.
diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp
index 723d87e..35bc637 100644
--- a/tools/aapt2/cmd/Util_test.cpp
+++ b/tools/aapt2/cmd/Util_test.cpp
@@ -383,21 +383,25 @@
TEST(UtilTest, ParseFeatureFlagsParameter_DuplicateFlag) {
auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
FeatureFlagValues feature_flag_values;
- ASSERT_TRUE(
- ParseFeatureFlagsParameter("foo=true,bar=true,foo=false", diagnostics, &feature_flag_values));
- EXPECT_THAT(feature_flag_values, UnorderedElementsAre(Pair("foo", std::optional<bool>(false)),
- Pair("bar", std::optional<bool>(true))));
+ ASSERT_TRUE(ParseFeatureFlagsParameter("foo=true,bar=true,foo:ro=false", diagnostics,
+ &feature_flag_values));
+ EXPECT_THAT(
+ feature_flag_values,
+ UnorderedElementsAre(Pair("foo", FeatureFlagProperties{true, std::optional<bool>(false)}),
+ Pair("bar", FeatureFlagProperties{false, std::optional<bool>(true)})));
}
TEST(UtilTest, ParseFeatureFlagsParameter_Valid) {
auto diagnostics = test::ContextBuilder().Build()->GetDiagnostics();
FeatureFlagValues feature_flag_values;
- ASSERT_TRUE(ParseFeatureFlagsParameter("foo= true, bar =FALSE,baz=, quux", diagnostics,
+ ASSERT_TRUE(ParseFeatureFlagsParameter("foo= true, bar:ro =FALSE,baz=, quux", diagnostics,
&feature_flag_values));
- EXPECT_THAT(feature_flag_values,
- UnorderedElementsAre(Pair("foo", std::optional<bool>(true)),
- Pair("bar", std::optional<bool>(false)),
- Pair("baz", std::nullopt), Pair("quux", std::nullopt)));
+ EXPECT_THAT(
+ feature_flag_values,
+ UnorderedElementsAre(Pair("foo", FeatureFlagProperties{false, std::optional<bool>(true)}),
+ Pair("bar", FeatureFlagProperties{true, std::optional<bool>(false)}),
+ Pair("baz", FeatureFlagProperties{false, std::nullopt}),
+ Pair("quux", FeatureFlagProperties{false, std::nullopt})));
}
TEST (UtilTest, AdjustSplitConstraintsForMinSdk) {
diff --git a/tools/aapt2/link/FeatureFlagsFilter.cpp b/tools/aapt2/link/FeatureFlagsFilter.cpp
index fdf3f74..9d40db5 100644
--- a/tools/aapt2/link/FeatureFlagsFilter.cpp
+++ b/tools/aapt2/link/FeatureFlagsFilter.cpp
@@ -63,12 +63,11 @@
flag_name = flag_name.substr(1);
}
- if (auto it = feature_flag_values_.find(std::string(flag_name));
- it != feature_flag_values_.end()) {
- if (it->second.has_value()) {
+ if (auto it = feature_flag_values_.find(flag_name); it != feature_flag_values_.end()) {
+ if (it->second.enabled.has_value()) {
if (options_.remove_disabled_elements) {
// Remove if flag==true && attr=="!flag" (negated) OR flag==false && attr=="flag"
- return *it->second == negated;
+ return *it->second.enabled == negated;
}
} else if (options_.flags_must_have_value) {
diagnostics_->Error(android::DiagMessage(node->line_number)
diff --git a/tools/aapt2/link/FeatureFlagsFilter_test.cpp b/tools/aapt2/link/FeatureFlagsFilter_test.cpp
index 53086cc..2db2899 100644
--- a/tools/aapt2/link/FeatureFlagsFilter_test.cpp
+++ b/tools/aapt2/link/FeatureFlagsFilter_test.cpp
@@ -48,7 +48,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
<permission android:name="FOO" />
</manifest>)EOF",
- {{"flag", false}});
+ {{"flag", FeatureFlagProperties{false, false}}});
ASSERT_THAT(doc, NotNull());
auto root = doc->root.get();
ASSERT_THAT(root, NotNull());
@@ -60,7 +60,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
<permission android:name="FOO" android:featureFlag="flag" />
</manifest>)EOF",
- {{"flag", false}});
+ {{"flag", FeatureFlagProperties{false, false}}});
ASSERT_THAT(doc, NotNull());
auto root = doc->root.get();
ASSERT_THAT(root, NotNull());
@@ -73,7 +73,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
<permission android:name="FOO" android:featureFlag="!flag" />
</manifest>)EOF",
- {{"flag", true}});
+ {{"flag", FeatureFlagProperties{false, true}}});
ASSERT_THAT(doc, NotNull());
auto root = doc->root.get();
ASSERT_THAT(root, NotNull());
@@ -86,7 +86,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
<permission android:name="FOO" android:featureFlag="flag" />
</manifest>)EOF",
- {{"flag", true}});
+ {{"flag", FeatureFlagProperties{false, true}}});
ASSERT_THAT(doc, NotNull());
auto root = doc->root.get();
ASSERT_THAT(root, NotNull());
@@ -102,7 +102,7 @@
<permission android:name="FOO" android:featureFlag="flag"
android:protectionLevel="dangerous" />
</manifest>)EOF",
- {{"flag", true}});
+ {{"flag", FeatureFlagProperties{false, true}}});
ASSERT_THAT(doc, NotNull());
auto root = doc->root.get();
ASSERT_THAT(root, NotNull());
@@ -123,7 +123,7 @@
</activity>
</application>
</manifest>)EOF",
- {{"flag", true}});
+ {{"flag", FeatureFlagProperties{false, true}}});
ASSERT_THAT(doc, NotNull());
auto root = doc->root.get();
ASSERT_THAT(root, NotNull());
@@ -145,7 +145,7 @@
</activity>
</application>
</manifest>)EOF",
- {{"flag", true}});
+ {{"flag", FeatureFlagProperties{false, true}}});
ASSERT_THAT(doc, NotNull());
auto root = doc->root.get();
ASSERT_THAT(root, NotNull());
@@ -162,7 +162,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
<permission android:name="FOO" android:featureFlag=" " />
</manifest>)EOF",
- {{"flag", false}});
+ {{"flag", FeatureFlagProperties{false, false}}});
ASSERT_THAT(doc, IsNull());
}
@@ -171,7 +171,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
<permission android:name="FOO" android:featureFlag="flag" />
</manifest>)EOF",
- {{"flag", std::nullopt}});
+ {{"flag", FeatureFlagProperties{false, std::nullopt}}});
ASSERT_THAT(doc, IsNull());
}
@@ -180,7 +180,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
<permission android:name="FOO" android:featureFlag="unrecognized" />
</manifest>)EOF",
- {{"flag", true}});
+ {{"flag", FeatureFlagProperties{false, true}}});
ASSERT_THAT(doc, IsNull());
}
@@ -190,7 +190,7 @@
<permission android:name="FOO" android:featureFlag="bar" />
<permission android:name="FOO" android:featureFlag="unrecognized" />
</manifest>)EOF",
- {{"flag", std::nullopt}});
+ {{"flag", FeatureFlagProperties{false, std::nullopt}}});
ASSERT_THAT(doc, IsNull());
}
@@ -199,7 +199,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
<permission android:name="FOO" android:featureFlag="flag" />
</manifest>)EOF",
- {{"flag", false}}, {.remove_disabled_elements = false});
+ {{"flag", FeatureFlagProperties{false, false}}},
+ {.remove_disabled_elements = false});
ASSERT_THAT(doc, NotNull());
auto root = doc->root.get();
ASSERT_THAT(root, NotNull());
@@ -212,7 +213,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
<permission android:name="FOO" android:featureFlag="flag" />
</manifest>)EOF",
- {{"flag", std::nullopt}}, {.flags_must_have_value = false});
+ {{"flag", FeatureFlagProperties{false, std::nullopt}}},
+ {.flags_must_have_value = false});
ASSERT_THAT(doc, NotNull());
auto root = doc->root.get();
ASSERT_THAT(root, NotNull());
@@ -225,7 +227,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android">
<permission android:name="FOO" android:featureFlag="unrecognized" />
</manifest>)EOF",
- {{"flag", true}}, {.fail_on_unrecognized_flags = false});
+ {{"flag", FeatureFlagProperties{false, true}}},
+ {.fail_on_unrecognized_flags = false});
ASSERT_THAT(doc, NotNull());
auto root = doc->root.get();
ASSERT_THAT(root, NotNull());