Merge "Add hidden API putPhoneIdAndMaybeSubIdExtra" 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/api/gen_combined_removed_dex.sh b/api/gen_combined_removed_dex.sh
index e0153f7..2860e2e 100755
--- a/api/gen_combined_removed_dex.sh
+++ b/api/gen_combined_removed_dex.sh
@@ -6,6 +6,6 @@
# Convert each removed.txt to the "dex format" equivalent, and print all output.
for f in "$@"; do
- "$metalava_path" signature-to-dex "$f" "${tmp_dir}/tmp"
+ "$metalava_path" signature-to-dex "$f" --out "${tmp_dir}/tmp"
cat "${tmp_dir}/tmp"
done
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 0ab2588..e4a8407 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -318,6 +318,14 @@
}
+package android.net.wifi {
+
+ public final class WifiMigration {
+ method @FlaggedApi("android.net.wifi.flags.legacy_keystore_to_wifi_blobstore_migration") public static void migrateLegacyKeystoreToWifiBlobstore();
+ }
+
+}
+
package android.nfc {
public class NfcServiceManager {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index b3c471c..4536f6f 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3447,6 +3447,7 @@
}
public static interface VirtualDeviceManager.ActivityListener {
+ method @FlaggedApi("android.companion.virtualdevice.flags.activity_control_api") public default void onActivityLaunchBlocked(int, @NonNull android.content.ComponentName, int);
method public void onDisplayEmpty(int);
method @Deprecated public void onTopActivityChanged(int, @NonNull android.content.ComponentName);
method public default void onTopActivityChanged(int, @NonNull android.content.ComponentName, int);
@@ -11399,8 +11400,9 @@
method public long getNumPacketsTx();
method public long getRxTimeMillis();
method public long getSleepTimeMillis();
- method @NonNull public long getTimeInRatMicros(int);
- method @NonNull public long getTimeInRxSignalStrengthLevelMicros(@IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) int);
+ method public long getTimeInRatMicros(int);
+ method public long getTimeInRxSignalStrengthLevelMicros(@IntRange(from=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN, to=android.telephony.CellSignalStrength.SIGNAL_STRENGTH_GREAT) int);
+ method @FlaggedApi("com.android.server.power.optimization.streamlined_connectivity_battery_stats") public long getTxTimeMillis(@IntRange(from=android.telephony.ModemActivityInfo.TX_POWER_LEVEL_0, to=android.telephony.ModemActivityInfo.TX_POWER_LEVEL_4) int);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.os.connectivity.CellularBatteryStats> CREATOR;
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 1352465..b7de93a 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2608,6 +2608,18 @@
}
+package android.os.connectivity {
+
+ public final class CellularBatteryStats implements android.os.Parcelable {
+ ctor @FlaggedApi("com.android.server.power.optimization.streamlined_connectivity_battery_stats") public CellularBatteryStats(long, long, long, long, long, long, long, long, long, long, @NonNull long[], @NonNull long[], @NonNull long[], long);
+ }
+
+ public final class WifiBatteryStats implements android.os.Parcelable {
+ ctor @FlaggedApi("com.android.server.power.optimization.streamlined_connectivity_battery_stats") public WifiBatteryStats(long, long, long, long, long, long, long, long, long, long, long, long, long, @NonNull long[], @NonNull long[], @NonNull long[], long);
+ }
+
+}
+
package android.os.health {
public class HealthKeys {
diff --git a/core/java/Android.bp b/core/java/Android.bp
index fae411d..128fb62 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -20,10 +20,43 @@
"**/*.java",
"**/*.aidl",
":framework-nfc-non-updatable-sources",
+ ":messagequeue-gen",
+ ],
+ // Exactly one of the below will be added to srcs by messagequeue-gen
+ exclude_srcs: [
+ "android/os/LegacyMessageQueue/MessageQueue.java",
+ "android/os/ConcurrentMessageQueue/MessageQueue.java",
+ "android/os/SemiConcurrentMessageQueue/MessageQueue.java",
],
visibility: ["//frameworks/base"],
}
+// Add selected MessageQueue.java implementation to srcs
+soong_config_module_type {
+ name: "release_package_messagequeue_implementation_srcs",
+ module_type: "genrule",
+ config_namespace: "messagequeue",
+ value_variables: ["release_package_messagequeue_implementation"],
+ properties: [
+ "srcs",
+ ],
+}
+
+// Output the selected android/os/MessageQueue.java implementation
+release_package_messagequeue_implementation_srcs {
+ name: "messagequeue-gen",
+ soong_config_variables: {
+ release_package_messagequeue_implementation: {
+ srcs: ["android/os/%s"],
+ conditions_default: {
+ srcs: ["android/os/LegacyMessageQueue/MessageQueue.java"],
+ },
+ },
+ },
+ cmd: "mkdir -p android/os/; cp $(in) $(out);",
+ out: ["android/os/MessageQueue.java"],
+}
+
aidl_library {
name: "IDropBoxManagerService_aidl",
srcs: [
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/IVirtualDeviceActivityListener.aidl b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
index fc7f85c..39371a3 100644
--- a/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
+++ b/core/java/android/companion/virtual/IVirtualDeviceActivityListener.aidl
@@ -32,7 +32,7 @@
* @param topActivity The component name of the top activity.
* @param userId The user ID associated with the top activity.
*/
- void onTopActivityChanged(int displayId, in ComponentName topActivity, in int userId);
+ void onTopActivityChanged(int displayId, in ComponentName topActivity, int userId);
/**
* Called when the display becomes empty (e.g. if the user hits back on the last
@@ -41,4 +41,13 @@
* @param displayId The display ID that became empty.
*/
void onDisplayEmpty(int displayId);
+
+ /**
+ * Called when an activity launch was blocked due to a policy violation.
+ *
+ * @param displayId The display ID on which the activity tried to launch.
+ * @param componentName The component name of the blocked activity.
+ * @param userId The user ID associated with the blocked activity.
+ */
+ void onActivityLaunchBlocked(int displayId, in ComponentName componentName, int userId);
}
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index af86c97..d3fcfc6 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -128,6 +128,22 @@
Binder.restoreCallingIdentity(token);
}
}
+
+ @Override
+ public void onActivityLaunchBlocked(int displayId, ComponentName componentName,
+ @UserIdInt int userId) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mActivityListenersLock) {
+ for (int i = 0; i < mActivityListeners.size(); i++) {
+ mActivityListeners.valueAt(i)
+ .onActivityLaunchBlocked(displayId, componentName, userId);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
};
private final IVirtualDeviceSoundEffectListener mSoundEffectListener =
new IVirtualDeviceSoundEffectListener.Stub() {
@@ -164,6 +180,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();
@@ -511,6 +541,12 @@
public void onDisplayEmpty(int displayId) {
mExecutor.execute(() -> mActivityListener.onDisplayEmpty(displayId));
}
+
+ public void onActivityLaunchBlocked(int displayId, ComponentName componentName,
+ @UserIdInt int userId) {
+ mExecutor.execute(() ->
+ mActivityListener.onActivityLaunchBlocked(displayId, componentName, userId));
+ }
}
/**
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index c68014d..296ca33 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.
*/
@@ -1120,7 +1126,7 @@
}
/**
- * Listener for activity changes in this virtual device.
+ * Listener for activity changes and other activity events on a virtual device.
*
* @hide
*/
@@ -1161,6 +1167,20 @@
* @param displayId The display ID that became empty.
*/
void onDisplayEmpty(int displayId);
+
+ /**
+ * Called when an activity launch was blocked due to a policy violation.
+ *
+ * @param displayId The display ID on which the activity tried to launch.
+ * @param componentName The component name of the blocked activity.
+ * @param userId The user ID associated with the blocked activity.
+ *
+ * @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
+ * @see VirtualDevice#addActivityPolicyExemption(ComponentName)
+ */
+ @FlaggedApi(android.companion.virtualdevice.flags.Flags.FLAG_ACTIVITY_CONTROL_API)
+ default void onActivityLaunchBlocked(int displayId, @NonNull ComponentName componentName,
+ @UserIdInt int userId) {}
}
/**
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index cd8082c..64d2081 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -41,6 +41,13 @@
flag {
namespace: "virtual_devices"
+ name: "activity_control_api"
+ description: "Enable APIs for fine grained activity policy, fallback and callbacks"
+ bug: "333443509"
+}
+
+flag {
+ namespace: "virtual_devices"
name: "camera_device_awareness"
description: "Enable device awareness in camera service"
bug: "305170199"
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index a9c07d1..6f5bb4a 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -126,7 +126,7 @@
description: "Talkback focus doesn't move to the 'If you change your Google Account picture…' after swiping next to move the focus from 'Choose a picture'"
bug: "330835921"
metadata {
- purpose: PURPOSE_BUGFIX
+ purpose: PURPOSE_BUGFIX
}
}
@@ -136,7 +136,7 @@
description: "Talkback doesn't announce 'selected' after double tapping the button in the picture list in 'Choose a picture' page."
bug: "330840549"
metadata {
- purpose: PURPOSE_BUGFIX
+ purpose: PURPOSE_BUGFIX
}
}
@@ -146,10 +146,21 @@
description: "Fix potential unexpected behavior due to concurrent file writing"
bug: "339351031"
metadata {
- purpose: PURPOSE_BUGFIX
+ purpose: PURPOSE_BUGFIX
}
}
+flag {
+ name: "cache_user_serial_number"
+ namespace: "multiuser"
+ description: "Optimise user serial number retrieval"
+ bug: "340018451"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+
# This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile.
flag {
name: "enable_private_space_features"
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/ColorStateList.java b/core/java/android/content/res/ColorStateList.java
index 5031faa..7b18117 100644
--- a/core/java/android/content/res/ColorStateList.java
+++ b/core/java/android/content/res/ColorStateList.java
@@ -32,6 +32,9 @@
import android.util.SparseArray;
import android.util.StateSet;
import android.util.Xml;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
import com.android.internal.R;
import com.android.internal.graphics.ColorUtils;
@@ -44,7 +47,9 @@
import java.io.IOException;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/**
*
@@ -793,4 +798,61 @@
return new ColorStateList(stateSpecs, colors);
}
};
+
+ /** @hide */
+ public void writeToProto(ProtoOutputStream out) {
+ for (int[] states : mStateSpecs) {
+ long specToken = out.start(ColorStateListProto.STATE_SPECS);
+ for (int state : states) {
+ out.write(ColorStateListProto.StateSpec.STATE, state);
+ }
+ out.end(specToken);
+ }
+ for (int color : mColors) {
+ out.write(ColorStateListProto.COLORS, color);
+ }
+ }
+
+ /** @hide */
+ public static ColorStateList createFromProto(ProtoInputStream in)
+ throws Exception {
+ List<int[]> stateSpecs = new ArrayList<>();
+ List<Integer> colors = new ArrayList<>();
+
+
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) ColorStateListProto.COLORS:
+ colors.add(in.readInt(ColorStateListProto.COLORS));
+ break;
+ case (int) ColorStateListProto.STATE_SPECS:
+ final long stateToken = in.start(ColorStateListProto.STATE_SPECS);
+ List<Integer> states = new ArrayList<>();
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) ColorStateListProto.StateSpec.STATE:
+ states.add(in.readInt(ColorStateListProto.StateSpec.STATE));
+ break;
+ default:
+ Log.w(TAG, "Unhandled field while reading Icon proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ int[] statesArray = new int[states.size()];
+ Arrays.setAll(statesArray, states::get);
+ stateSpecs.add(statesArray);
+ in.end(stateToken);
+ break;
+ default:
+ Log.w(TAG, "Unhandled field while reading Icon proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+
+ int[][] stateSpecsArray = new int[stateSpecs.size()][];
+ Arrays.setAll(stateSpecsArray, stateSpecs::get);
+ int[] colorsArray = new int[colors.size()];
+ Arrays.setAll(colorsArray, colors::get);
+ return new ColorStateList(stateSpecsArray, colorsArray);
+ }
}
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/Camera.java b/core/java/android/hardware/Camera.java
index c2424e8..cbac912 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -32,6 +32,7 @@
import android.app.AppOpsManager;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.AttributionSource.ScopedParcelState;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Point;
@@ -45,6 +46,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -293,10 +295,14 @@
@SuppressLint("UnflaggedApi") // @TestApi without associated feature.
@TestApi
public static int getNumberOfCameras(@NonNull Context context) {
- return _getNumberOfCameras(context.getDeviceId(), getDevicePolicyFromContext(context));
+ try (ScopedParcelState clientAttribution =
+ context.getAttributionSource().asScopedParcelState()) {
+ return _getNumberOfCameras(
+ clientAttribution.getParcel(), getDevicePolicyFromContext(context));
+ }
}
- private static native int _getNumberOfCameras(int deviceId, int devicePolicy);
+ private static native int _getNumberOfCameras(Parcel clientAttributionParcel, int devicePolicy);
/**
* Returns the information about a particular camera.
@@ -321,8 +327,16 @@
@TestApi
public static void getCameraInfo(int cameraId, @NonNull Context context,
int rotationOverride, CameraInfo cameraInfo) {
- _getCameraInfo(cameraId, rotationOverride, context.getDeviceId(),
- getDevicePolicyFromContext(context), cameraInfo);
+ try (ScopedParcelState clientAttribution =
+ context.getAttributionSource().asScopedParcelState()) {
+ _getCameraInfo(
+ cameraId,
+ rotationOverride,
+ clientAttribution.getParcel(),
+ getDevicePolicyFromContext(context),
+ cameraInfo);
+ }
+
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
IAudioService audioService = IAudioService.Stub.asInterface(b);
try {
@@ -336,8 +350,12 @@
}
}
- private native static void _getCameraInfo(int cameraId, int rotationOverride,
- int deviceId, int devicePolicy, CameraInfo cameraInfo);
+ private native static void _getCameraInfo(
+ int cameraId,
+ int rotationOverride,
+ Parcel clientAttributionParcel,
+ int devicePolicy,
+ CameraInfo cameraInfo);
private static int getDevicePolicyFromContext(Context context) {
if (context.getDeviceId() == DEVICE_ID_DEFAULT
@@ -545,9 +563,18 @@
}
boolean forceSlowJpegMode = shouldForceSlowJpegMode();
- return native_setup(new WeakReference<>(this), cameraId,
- ActivityThread.currentOpPackageName(), rotationOverride, forceSlowJpegMode,
- context.getDeviceId(), getDevicePolicyFromContext(context));
+
+ try (ScopedParcelState clientAttribution =
+ context.getAttributionSource().asScopedParcelState()) {
+ return native_setup(
+ new WeakReference<>(this),
+ cameraId,
+ ActivityThread.currentOpPackageName(),
+ rotationOverride,
+ forceSlowJpegMode,
+ clientAttribution.getParcel(),
+ getDevicePolicyFromContext(context));
+ }
}
private boolean shouldForceSlowJpegMode() {
@@ -630,8 +657,14 @@
}
@UnsupportedAppUsage
- private native int native_setup(Object cameraThis, int cameraId, String packageName,
- int rotationOverride, boolean forceSlowJpegMode, int deviceId, int devicePolicy);
+ private native int native_setup(
+ Object cameraThis,
+ int cameraId,
+ String packageName,
+ int rotationOverride,
+ boolean forceSlowJpegMode,
+ Parcel clientAttributionParcel,
+ int devicePolicy);
private native final void native_release();
@@ -2267,9 +2300,11 @@
private static final String KEY_MIN_EXPOSURE_COMPENSATION = "min-exposure-compensation";
private static final String KEY_EXPOSURE_COMPENSATION_STEP = "exposure-compensation-step";
private static final String KEY_AUTO_EXPOSURE_LOCK = "auto-exposure-lock";
- private static final String KEY_AUTO_EXPOSURE_LOCK_SUPPORTED = "auto-exposure-lock-supported";
+ private static final String KEY_AUTO_EXPOSURE_LOCK_SUPPORTED =
+ "auto-exposure-lock-supported";
private static final String KEY_AUTO_WHITEBALANCE_LOCK = "auto-whitebalance-lock";
- private static final String KEY_AUTO_WHITEBALANCE_LOCK_SUPPORTED = "auto-whitebalance-lock-supported";
+ private static final String KEY_AUTO_WHITEBALANCE_LOCK_SUPPORTED =
+ "auto-whitebalance-lock-supported";
private static final String KEY_METERING_AREAS = "metering-areas";
private static final String KEY_MAX_NUM_METERING_AREAS = "max-num-metering-areas";
private static final String KEY_ZOOM = "zoom";
@@ -2286,7 +2321,8 @@
private static final String KEY_RECORDING_HINT = "recording-hint";
private static final String KEY_VIDEO_SNAPSHOT_SUPPORTED = "video-snapshot-supported";
private static final String KEY_VIDEO_STABILIZATION = "video-stabilization";
- private static final String KEY_VIDEO_STABILIZATION_SUPPORTED = "video-stabilization-supported";
+ private static final String KEY_VIDEO_STABILIZATION_SUPPORTED =
+ "video-stabilization-supported";
// Parameter key suffix for supported values.
private static final String SUPPORTED_VALUES_SUFFIX = "-values";
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 9eb9745..2dbd4b8 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -35,6 +35,7 @@
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.Overridable;
+import android.content.AttributionSourceState;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Point;
@@ -106,6 +107,7 @@
private final boolean DEBUG = false;
private static final int USE_CALLING_UID = -1;
+ private static final int USE_CALLING_PID = -1;
@SuppressWarnings("unused")
private static final int API_VERSION_1 = 1;
@@ -418,9 +420,12 @@
public boolean isConcurrentSessionConfigurationSupported(
@NonNull Map<String, SessionConfiguration> cameraIdAndSessionConfig)
throws CameraAccessException {
- return CameraManagerGlobal.get().isConcurrentSessionConfigurationSupported(
- cameraIdAndSessionConfig, mContext.getApplicationInfo().targetSdkVersion,
- mContext.getDeviceId(), getDevicePolicyFromContext(mContext));
+ return CameraManagerGlobal.get()
+ .isConcurrentSessionConfigurationSupported(
+ cameraIdAndSessionConfig,
+ mContext.getApplicationInfo().targetSdkVersion,
+ getClientAttribution(),
+ getDevicePolicyFromContext(mContext));
}
/**
@@ -660,11 +665,14 @@
}
try {
for (String physicalCameraId : physicalCameraIds) {
+ AttributionSourceState clientAttribution = getClientAttribution();
+ clientAttribution.deviceId = DEVICE_ID_DEFAULT;
CameraMetadataNative physicalCameraInfo =
- cameraService.getCameraCharacteristics(physicalCameraId,
+ cameraService.getCameraCharacteristics(
+ physicalCameraId,
mContext.getApplicationInfo().targetSdkVersion,
/*rotationOverride*/ ICameraService.ROTATION_OVERRIDE_NONE,
- DEVICE_ID_DEFAULT,
+ clientAttribution,
DEVICE_POLICY_DEFAULT);
StreamConfiguration[] configs = physicalCameraInfo.get(
CameraCharacteristics.
@@ -756,9 +764,13 @@
"Camera service is currently unavailable");
}
try {
- CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId,
- mContext.getApplicationInfo().targetSdkVersion, rotationOverride,
- mContext.getDeviceId(), getDevicePolicyFromContext(mContext));
+ CameraMetadataNative info =
+ cameraService.getCameraCharacteristics(
+ cameraId,
+ mContext.getApplicationInfo().targetSdkVersion,
+ rotationOverride,
+ getClientAttribution(),
+ getDevicePolicyFromContext(mContext));
characteristics = prepareCameraCharacteristics(cameraId, info, cameraService);
} catch (ServiceSpecificException e) {
throw ExceptionUtils.throwAsPublicException(e);
@@ -951,15 +963,33 @@
}
/**
+ * Constructs an AttributionSourceState with only the uid, pid, and deviceId fields set
+ *
+ * <p>This method is a temporary stopgap in the transition to using AttributionSource. Currently
+ * AttributionSourceState is only used as a vehicle for passing deviceId, uid, and pid
+ * arguments.</p>
+ *
+ * @hide
+ */
+ public AttributionSourceState getClientAttribution() {
+ // TODO: Send the full contextAttribution over aidl, remove USE_CALLING_*
+ AttributionSourceState contextAttribution =
+ mContext.getAttributionSource().asState();
+ AttributionSourceState clientAttribution =
+ new AttributionSourceState();
+ clientAttribution.uid = USE_CALLING_UID;
+ clientAttribution.pid = USE_CALLING_PID;
+ clientAttribution.deviceId = contextAttribution.deviceId;
+ clientAttribution.next = new AttributionSourceState[0];
+ return clientAttribution;
+ }
+
+ /**
* Helper for opening a connection to a camera with the given ID.
*
* @param cameraId The unique identifier of the camera device to open
* @param callback The callback for the camera. Must not be null.
* @param executor The executor to invoke the callback with. Must not be null.
- * @param uid The UID of the application actually opening the camera.
- * Must be USE_CALLING_UID unless the caller is a service
- * that is trusted to open the device on behalf of an
- * application and to forward the real UID.
*
* @throws CameraAccessException if the camera is disabled by device policy,
* too many camera devices are already open, or the cameraId does not match
@@ -974,7 +1004,7 @@
* @see android.app.admin.DevicePolicyManager#setCameraDisabled
*/
private CameraDevice openCameraDeviceUserAsync(String cameraId,
- CameraDevice.StateCallback callback, Executor executor, final int uid,
+ CameraDevice.StateCallback callback, Executor executor,
final int oomScoreOffset, int rotationOverride) throws CameraAccessException {
CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
CameraDevice device = null;
@@ -1005,11 +1035,19 @@
"Camera service is currently unavailable");
}
- cameraUser = cameraService.connectDevice(callbacks, cameraId,
- mContext.getOpPackageName(), mContext.getAttributionTag(), uid,
- oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion,
- rotationOverride, mContext.getDeviceId(),
- getDevicePolicyFromContext(mContext));
+ AttributionSourceState clientAttribution =
+ getClientAttribution();
+ cameraUser =
+ cameraService.connectDevice(
+ callbacks,
+ cameraId,
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
+ oomScoreOffset,
+ mContext.getApplicationInfo().targetSdkVersion,
+ rotationOverride,
+ clientAttribution,
+ getDevicePolicyFromContext(mContext));
} catch (ServiceSpecificException e) {
if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
throw new AssertionError("Should've gone down the shim path");
@@ -1135,8 +1173,8 @@
public void openCamera(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback, @Nullable Handler handler)
throws CameraAccessException {
- openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
- USE_CALLING_UID);
+
+ openCameraImpl(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler));
}
/**
@@ -1172,8 +1210,8 @@
public void openCamera(@NonNull String cameraId, boolean overrideToPortrait,
@Nullable Handler handler,
@NonNull final CameraDevice.StateCallback callback) throws CameraAccessException {
- openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
- USE_CALLING_UID, /*oomScoreOffset*/0,
+ openCameraImpl(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler),
+ /*oomScoreOffset*/0,
overrideToPortrait
? ICameraService.ROTATION_OVERRIDE_OVERRIDE_TO_PORTRAIT
: ICameraService.ROTATION_OVERRIDE_NONE);
@@ -1221,7 +1259,7 @@
if (executor == null) {
throw new IllegalArgumentException("executor was null");
}
- openCameraForUid(cameraId, callback, executor, USE_CALLING_UID);
+ openCameraImpl(cameraId, callback, executor);
}
/**
@@ -1289,13 +1327,13 @@
throw new IllegalArgumentException(
"oomScoreOffset < 0, cannot increase priority of camera client");
}
- openCameraForUid(cameraId, callback, executor, USE_CALLING_UID, oomScoreOffset,
+ openCameraImpl(cameraId, callback, executor, oomScoreOffset,
getRotationOverride(mContext));
}
/**
- * Open a connection to a camera with the given ID, on behalf of another application
- * specified by clientUid. Also specify the minimum oom score and process state the application
+ * Open a connection to a camera with the given ID, on behalf of another application.
+ * Also specify the minimum oom score and process state the application
* should have, as seen by the cameraserver.
*
* <p>The behavior of this method matches that of {@link #openCamera}, except that it allows
@@ -1303,9 +1341,6 @@
* done by services trusted by the camera subsystem to act on behalf of applications and
* to forward the real UID.</p>
*
- * @param clientUid
- * The UID of the application on whose behalf the camera is being opened.
- * Must be USE_CALLING_UID unless the caller is a trusted service.
* @param oomScoreOffset
* The minimum oom score that cameraservice must see for this client.
* @param rotationOverride
@@ -1313,9 +1348,9 @@
* that should be followed for this camera id connection
* @hide
*/
- public void openCameraForUid(@NonNull String cameraId,
+ public void openCameraImpl(@NonNull String cameraId,
@NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
- int clientUid, int oomScoreOffset, int rotationOverride)
+ int oomScoreOffset, int rotationOverride)
throws CameraAccessException {
if (cameraId == null) {
@@ -1327,29 +1362,24 @@
throw new IllegalArgumentException("No cameras available on device");
}
- openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset,
+ openCameraDeviceUserAsync(cameraId, callback, executor, oomScoreOffset,
rotationOverride);
}
/**
- * Open a connection to a camera with the given ID, on behalf of another application
- * specified by clientUid.
+ * Open a connection to a camera with the given ID, on behalf of another application.
*
* <p>The behavior of this method matches that of {@link #openCamera}, except that it allows
* the caller to specify the UID to use for permission/etc verification. This can only be
* done by services trusted by the camera subsystem to act on behalf of applications and
* to forward the real UID.</p>
*
- * @param clientUid
- * The UID of the application on whose behalf the camera is being opened.
- * Must be USE_CALLING_UID unless the caller is a trusted service.
- *
* @hide
*/
- public void openCameraForUid(@NonNull String cameraId,
- @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor,
- int clientUid) throws CameraAccessException {
- openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0,
+ public void openCameraImpl(@NonNull String cameraId,
+ @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor)
+ throws CameraAccessException {
+ openCameraImpl(cameraId, callback, executor, /*oomScoreOffset*/0,
getRotationOverride(mContext));
}
@@ -1397,8 +1427,12 @@
if (CameraManagerGlobal.sCameraServiceDisabled) {
throw new IllegalArgumentException("No cameras available on device");
}
- CameraManagerGlobal.get().setTorchMode(cameraId, enabled, mContext.getDeviceId(),
- getDevicePolicyFromContext(mContext));
+ CameraManagerGlobal.get()
+ .setTorchMode(
+ cameraId,
+ enabled,
+ getClientAttribution(),
+ getDevicePolicyFromContext(mContext));
}
/**
@@ -1461,8 +1495,12 @@
if (CameraManagerGlobal.sCameraServiceDisabled) {
throw new IllegalArgumentException("No camera available on device");
}
- CameraManagerGlobal.get().turnOnTorchWithStrengthLevel(cameraId, torchStrength,
- mContext.getDeviceId(), getDevicePolicyFromContext(mContext));
+ CameraManagerGlobal.get()
+ .turnOnTorchWithStrengthLevel(
+ cameraId,
+ torchStrength,
+ getClientAttribution(),
+ getDevicePolicyFromContext(mContext));
}
/**
@@ -1488,8 +1526,11 @@
if (CameraManagerGlobal.sCameraServiceDisabled) {
throw new IllegalArgumentException("No camera available on device.");
}
- return CameraManagerGlobal.get().getTorchStrengthLevel(cameraId, mContext.getDeviceId(),
- getDevicePolicyFromContext(mContext));
+ return CameraManagerGlobal.get()
+ .getTorchStrengthLevel(
+ cameraId,
+ getClientAttribution(),
+ getDevicePolicyFromContext(mContext));
}
/**
@@ -2499,7 +2540,9 @@
public boolean isConcurrentSessionConfigurationSupported(
@NonNull Map<String, SessionConfiguration> cameraIdsAndSessionConfigurations,
- int targetSdkVersion, int deviceId, int devicePolicy)
+ int targetSdkVersion,
+ AttributionSourceState clientAttribution,
+ int devicePolicy)
throws CameraAccessException {
if (cameraIdsAndSessionConfigurations == null) {
throw new IllegalArgumentException("cameraIdsAndSessionConfigurations was null");
@@ -2517,9 +2560,12 @@
for (Set<DeviceCameraInfo> combination : mConcurrentCameraIdCombinations) {
Set<DeviceCameraInfo> infos = new ArraySet<>();
for (String cameraId : cameraIdsAndSessionConfigurations.keySet()) {
- infos.add(new DeviceCameraInfo(cameraId,
- devicePolicy == DEVICE_POLICY_DEFAULT
- ? DEVICE_ID_DEFAULT : deviceId));
+ infos.add(
+ new DeviceCameraInfo(
+ cameraId,
+ devicePolicy == DEVICE_POLICY_DEFAULT
+ ? DEVICE_ID_DEFAULT
+ : clientAttribution.deviceId));
}
if (combination.containsAll(infos)) {
subsetFound = true;
@@ -2541,7 +2587,7 @@
}
try {
return mCameraService.isConcurrentSessionConfigurationSupported(
- cameraIdsAndConfigs, targetSdkVersion, deviceId, devicePolicy);
+ cameraIdsAndConfigs, targetSdkVersion, clientAttribution, devicePolicy);
} catch (ServiceSpecificException e) {
throw ExceptionUtils.throwAsPublicException(e);
} catch (RemoteException e) {
@@ -2580,7 +2626,11 @@
return false;
}
- public void setTorchMode(String cameraId, boolean enabled, int deviceId, int devicePolicy)
+ public void setTorchMode(
+ String cameraId,
+ boolean enabled,
+ AttributionSourceState clientAttribution,
+ int devicePolicy)
throws CameraAccessException {
synchronized (mLock) {
if (cameraId == null) {
@@ -2594,8 +2644,8 @@
}
try {
- cameraService.setTorchMode(cameraId, enabled, mTorchClientBinder, deviceId,
- devicePolicy);
+ cameraService.setTorchMode(
+ cameraId, enabled, mTorchClientBinder, clientAttribution, devicePolicy);
} catch(ServiceSpecificException e) {
throw ExceptionUtils.throwAsPublicException(e);
} catch (RemoteException e) {
@@ -2605,7 +2655,10 @@
}
}
- public void turnOnTorchWithStrengthLevel(String cameraId, int torchStrength, int deviceId,
+ public void turnOnTorchWithStrengthLevel(
+ String cameraId,
+ int torchStrength,
+ AttributionSourceState clientAttribution,
int devicePolicy)
throws CameraAccessException {
synchronized (mLock) {
@@ -2620,8 +2673,12 @@
}
try {
- cameraService.turnOnTorchWithStrengthLevel(cameraId, torchStrength,
- mTorchClientBinder, deviceId, devicePolicy);
+ cameraService.turnOnTorchWithStrengthLevel(
+ cameraId,
+ torchStrength,
+ mTorchClientBinder,
+ clientAttribution,
+ devicePolicy);
} catch(ServiceSpecificException e) {
throw ExceptionUtils.throwAsPublicException(e);
} catch (RemoteException e) {
@@ -2631,7 +2688,8 @@
}
}
- public int getTorchStrengthLevel(String cameraId, int deviceId, int devicePolicy)
+ public int getTorchStrengthLevel(
+ String cameraId, AttributionSourceState clientAttribution, int devicePolicy)
throws CameraAccessException {
int torchStrength;
synchronized (mLock) {
@@ -2646,8 +2704,9 @@
}
try {
- torchStrength = cameraService.getTorchStrengthLevel(cameraId, deviceId,
- devicePolicy);
+ torchStrength =
+ cameraService.getTorchStrengthLevel(
+ cameraId, clientAttribution, devicePolicy);
} catch(ServiceSpecificException e) {
throw ExceptionUtils.throwAsPublicException(e);
} catch (RemoteException e) {
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
index df057a1..4ddf602 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceSetupImpl.java
@@ -71,9 +71,12 @@
}
try {
- CameraMetadataNative defaultRequest = cameraService.createDefaultRequest(mCameraId,
- templateType, mContext.getDeviceId(),
- mCameraManager.getDevicePolicyFromContext(mContext));
+ CameraMetadataNative defaultRequest =
+ cameraService.createDefaultRequest(
+ mCameraId,
+ templateType,
+ mCameraManager.getClientAttribution(),
+ mCameraManager.getDevicePolicyFromContext(mContext));
CameraDeviceImpl.disableZslIfNeeded(defaultRequest, mTargetSdkVersion,
templateType);
@@ -104,9 +107,11 @@
}
try {
- return cameraService.isSessionConfigurationWithParametersSupported(mCameraId,
- mTargetSdkVersion, config,
- mContext.getDeviceId(),
+ return cameraService.isSessionConfigurationWithParametersSupported(
+ mCameraId,
+ mTargetSdkVersion,
+ config,
+ mCameraManager.getClientAttribution(),
mCameraManager.getDevicePolicyFromContext(mContext));
} catch (ServiceSpecificException e) {
throw ExceptionUtils.throwAsPublicException(e);
@@ -133,12 +138,14 @@
}
try {
- CameraMetadataNative metadata = cameraService.getSessionCharacteristics(
- mCameraId, mTargetSdkVersion,
- CameraManager.getRotationOverride(mContext),
- sessionConfig,
- mContext.getDeviceId(),
- mCameraManager.getDevicePolicyFromContext(mContext));
+ CameraMetadataNative metadata =
+ cameraService.getSessionCharacteristics(
+ mCameraId,
+ mTargetSdkVersion,
+ CameraManager.getRotationOverride(mContext),
+ sessionConfig,
+ mCameraManager.getClientAttribution(),
+ mCameraManager.getDevicePolicyFromContext(mContext));
return mCameraManager.prepareCameraCharacteristics(mCameraId, metadata,
cameraService);
diff --git a/core/java/android/hardware/devicestate/feature/flags.aconfig b/core/java/android/hardware/devicestate/feature/flags.aconfig
index 12d3f94..a09c84d 100644
--- a/core/java/android/hardware/devicestate/feature/flags.aconfig
+++ b/core/java/android/hardware/devicestate/feature/flags.aconfig
@@ -8,4 +8,13 @@
description: "Updated DeviceState hasProperty API"
bug: "293636629"
is_fixed_read_only: true
+}
+
+flag {
+ name: "device_state_property_migration"
+ is_exported: true
+ namespace: "windowing_sdk"
+ description: "Client migration to updated DeviceStateManager API's"
+ bug: "336640888"
+ is_fixed_read_only: true
}
\ No newline at end of file
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/os/BatteryUsageStats.java b/core/java/android/os/BatteryUsageStats.java
index 90d82e7..61cc23d 100644
--- a/core/java/android/os/BatteryUsageStats.java
+++ b/core/java/android/os/BatteryUsageStats.java
@@ -870,6 +870,16 @@
}
/**
+ * Returns true if this Builder is configured to hold data for the specified
+ * custom power component ID.
+ */
+ public boolean isSupportedCustomPowerComponent(int componentId) {
+ return componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ && componentId < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
+ + mBatteryConsumerDataLayout.customPowerComponentCount;
+ }
+
+ /**
* Constructs a read-only object using the Builder values.
*/
@NonNull
diff --git a/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
new file mode 100644
index 0000000..72b5cf7
--- /dev/null
+++ b/core/java/android/os/ConcurrentMessageQueue/MessageQueue.java
@@ -0,0 +1,1648 @@
+/*
+ * 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 android.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Handler;
+import android.os.Trace;
+import android.util.Log;
+import android.util.Printer;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Low-level class holding the list of messages to be dispatched by a
+ * {@link Looper}. Messages are not added directly to a MessageQueue,
+ * but rather through {@link Handler} objects associated with the Looper.
+ *
+ * <p>You can retrieve the MessageQueue for the current thread with
+ * {@link Looper#myQueue() Looper.myQueue()}.
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+ "com.android.platform.test.ravenwood.nativesubstitution.MessageQueue_host")
+public final class MessageQueue {
+ private static final String TAG = "ConcurrentMessageQueue";
+ private static final boolean DEBUG = false;
+ private static final boolean TRACE = false;
+
+ // True if the message queue can be quit.
+ private final boolean mQuitAllowed;
+
+ @SuppressWarnings("unused")
+ private long mPtr; // used by native code
+
+ @IntDef(value = {
+ STACK_NODE_MESSAGE,
+ STACK_NODE_ACTIVE,
+ STACK_NODE_PARKED,
+ STACK_NODE_TIMEDPARK})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface StackNodeType {}
+
+ /*
+ * Stack node types. STACK_NODE_MESSAGE indicates a node containing a message.
+ * The other types indicate what state our Looper thread is in. The bottom of
+ * the stack is always a single state node. Message nodes are added on top.
+ */
+ private static final int STACK_NODE_MESSAGE = 0;
+ /*
+ * Active state indicates that next() is processing messages
+ */
+ private static final int STACK_NODE_ACTIVE = 1;
+ /*
+ * Parked state indicates that the Looper thread is sleeping indefinitely (nothing to deliver)
+ */
+ private static final int STACK_NODE_PARKED = 2;
+ /*
+ * Timed Park state indicates that the Looper thread is sleeping, waiting for a message
+ * deadline
+ */
+ private static final int STACK_NODE_TIMEDPARK = 3;
+
+ /* Describes a node in the Treiber stack */
+ static class StackNode {
+ @StackNodeType
+ private final int mType;
+
+ StackNode(@StackNodeType int type) {
+ mType = type;
+ }
+
+ @StackNodeType
+ final int getNodeType() {
+ return mType;
+ }
+
+ final boolean isMessageNode() {
+ return mType == STACK_NODE_MESSAGE;
+ }
+ }
+
+ static final class MessageNode extends StackNode implements Comparable<MessageNode> {
+ private final Message mMessage;
+ volatile StackNode mNext;
+ StateNode mBottomOfStack;
+ boolean mWokeUp;
+ final long mInsertSeq;
+ private static final VarHandle sRemovedFromStack;
+ private volatile boolean mRemovedFromStackValue;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sRemovedFromStack = l.findVarHandle(MessageQueue.MessageNode.class,
+ "mRemovedFromStackValue", boolean.class);
+ } catch (Exception e) {
+ Log.wtf(TAG, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ MessageNode(@NonNull Message message, long insertSeq) {
+ super(STACK_NODE_MESSAGE);
+ mMessage = message;
+ mInsertSeq = insertSeq;
+ }
+
+ long getWhen() {
+ return mMessage.when;
+ }
+
+ boolean isRemovedFromStack() {
+ return mRemovedFromStackValue;
+ }
+
+ boolean removeFromStack() {
+ return sRemovedFromStack.compareAndSet(this, false, true);
+ }
+
+ boolean isAsync() {
+ return mMessage.isAsynchronous();
+ }
+
+ boolean isBarrier() {
+ return mMessage.target == null;
+ }
+
+ @Override
+ public int compareTo(@NonNull MessageNode messageNode) {
+ Message other = messageNode.mMessage;
+
+ int compared = Long.compare(mMessage.when, other.when);
+ if (compared == 0) {
+ compared = Long.compare(mInsertSeq, messageNode.mInsertSeq);
+ }
+ return compared;
+ }
+ }
+
+ static class StateNode extends StackNode {
+ StateNode(int type) {
+ super(type);
+ }
+ }
+
+ static final class TimedParkStateNode extends StateNode {
+ long mWhenToWake;
+
+ TimedParkStateNode() {
+ super(STACK_NODE_TIMEDPARK);
+ }
+ }
+
+ private static final StateNode sStackStateActive = new StateNode(STACK_NODE_ACTIVE);
+ private static final StateNode sStackStateParked = new StateNode(STACK_NODE_PARKED);
+ private final TimedParkStateNode mStackStateTimedPark = new TimedParkStateNode();
+
+ /* This is the top of our treiber stack. */
+ private static final VarHandle sState;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sState = l.findVarHandle(MessageQueue.class, "mStateValue",
+ MessageQueue.StackNode.class);
+ } catch (Exception e) {
+ Log.wtf(TAG, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ private volatile StackNode mStateValue = sStackStateParked;
+ private final ConcurrentSkipListSet<MessageNode> mPriorityQueue =
+ new ConcurrentSkipListSet<MessageNode>();
+ private final ConcurrentSkipListSet<MessageNode> mAsyncPriorityQueue =
+ new ConcurrentSkipListSet<MessageNode>();
+
+ /*
+ * This helps us ensure that messages with the same timestamp are inserted in FIFO order.
+ * Increments on each insert, starting at 0. MessageNode.compareTo() will compare sequences
+ * when delivery timestamps are identical.
+ */
+ private static final VarHandle sNextInsertSeq;
+ private volatile long mNextInsertSeqValue = 0;
+ /*
+ * The exception to the FIFO order rule is sendMessageAtFrontOfQueue().
+ * Those messages must be in LIFO order - SIGH.
+ * Decrements on each front of queue insert.
+ */
+ private static final VarHandle sNextFrontInsertSeq;
+ private volatile long mNextFrontInsertSeqValue = -1;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sNextInsertSeq = l.findVarHandle(MessageQueue.class, "mNextInsertSeqValue",
+ long.class);
+ sNextFrontInsertSeq = l.findVarHandle(MessageQueue.class, "mNextFrontInsertSeqValue",
+ long.class);
+ } catch (Exception e) {
+ Log.wtf(TAG, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+
+ }
+
+ /*
+ * Tracks the number of queued and cancelled messages in our stack.
+ *
+ * On item cancellation, determine whether to wake next() to flush tombstoned messages.
+ * We track queued and cancelled counts as two ints packed into a single long.
+ */
+ private static final class MessageCounts {
+ private static VarHandle sCounts;
+ private volatile long mCountsValue = 0;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sCounts = l.findVarHandle(MessageQueue.MessageCounts.class, "mCountsValue",
+ long.class);
+ } catch (Exception e) {
+ Log.wtf(TAG, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ /* We use a special value to indicate when next() has been woken for flush. */
+ private static final long AWAKE = Long.MAX_VALUE;
+ /*
+ * Minimum number of messages in the stack which we need before we consider flushing
+ * tombstoned items.
+ */
+ private static final int MESSAGE_FLUSH_THRESHOLD = 10;
+
+ private static int numQueued(long val) {
+ return (int) (val >>> Integer.SIZE);
+ }
+
+ private static int numCancelled(long val) {
+ return (int) val;
+ }
+
+ private static long combineCounts(int queued, int cancelled) {
+ return ((long) queued << Integer.SIZE) | (long) cancelled;
+ }
+
+ public void incrementQueued() {
+ while (true) {
+ long oldVal = mCountsValue;
+ int queued = numQueued(oldVal);
+ int cancelled = numCancelled(oldVal);
+ /* Use Math.max() to avoid overflow of queued count */
+ long newVal = combineCounts(Math.max(queued + 1, queued), cancelled);
+
+ /* Don't overwrite 'AWAKE' state */
+ if (oldVal == AWAKE || sCounts.compareAndSet(this, oldVal, newVal)) {
+ break;
+ }
+ }
+ }
+
+ public boolean incrementCancelled() {
+ while (true) {
+ long oldVal = mCountsValue;
+ if (oldVal == AWAKE) {
+ return false;
+ }
+ int queued = numQueued(oldVal);
+ int cancelled = numCancelled(oldVal);
+ boolean needsPurge = queued > MESSAGE_FLUSH_THRESHOLD
+ && (queued >> 1) < cancelled;
+ long newVal;
+ if (needsPurge) {
+ newVal = AWAKE;
+ } else {
+ newVal = combineCounts(queued,
+ Math.max(cancelled + 1, cancelled));
+ }
+
+ if (sCounts.compareAndSet(this, oldVal, newVal)) {
+ return needsPurge;
+ }
+ }
+ }
+
+ public void clearCounts() {
+ mCountsValue = 0;
+ }
+ }
+
+ private final MessageCounts mMessageCounts = new MessageCounts();
+
+ private final Object mIdleHandlersLock = new Object();
+ @GuardedBy("mIdleHandlersLock")
+ private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
+ private IdleHandler[] mPendingIdleHandlers;
+
+ private final Object mFileDescriptorRecordsLock = new Object();
+ @GuardedBy("mFileDescriptorRecordsLock")
+ private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
+
+ private static final VarHandle sQuitting;
+ private boolean mQuittingValue = false;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sQuitting = l.findVarHandle(MessageQueue.class, "mQuittingValue", boolean.class);
+ } catch (Exception e) {
+ Log.wtf(TAG, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ // The next barrier token.
+ // Barriers are indicated by messages with a null target whose arg1 field carries the token.
+ private final AtomicInteger mNextBarrierToken = new AtomicInteger(1);
+
+ private static native long nativeInit();
+ private static native void nativeDestroy(long ptr);
+ private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
+ private static native void nativeWake(long ptr);
+ private static native boolean nativeIsPolling(long ptr);
+ private static native void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
+
+ MessageQueue(boolean quitAllowed) {
+ mQuitAllowed = quitAllowed;
+ mPtr = nativeInit();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ // Disposes of the underlying message queue.
+ // Must only be called on the looper thread or the finalizer.
+ private void dispose() {
+ if (mPtr != 0) {
+ nativeDestroy(mPtr);
+ mPtr = 0;
+ }
+ }
+
+ /**
+ * Returns true if the looper has no pending messages which are due to be processed.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @return True if the looper is idle.
+ */
+ public boolean isIdle() {
+ MessageNode msgNode = null;
+ MessageNode asyncMsgNode = null;
+
+ if (!mPriorityQueue.isEmpty()) {
+ try {
+ msgNode = mPriorityQueue.first();
+ } catch (NoSuchElementException e) { }
+ }
+
+ if (!mAsyncPriorityQueue.isEmpty()) {
+ try {
+ asyncMsgNode = mAsyncPriorityQueue.first();
+ } catch (NoSuchElementException e) { }
+ }
+
+ final long now = SystemClock.uptimeMillis();
+ if ((msgNode != null && msgNode.getWhen() <= now)
+ || (asyncMsgNode != null && asyncMsgNode.getWhen() <= now)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /* Protects mNextIsDrainingStack */
+ private final ReentrantLock mDrainingLock = new ReentrantLock();
+ private boolean mNextIsDrainingStack = false;
+ private final Condition mDrainCompleted = mDrainingLock.newCondition();
+
+ /**
+ * Add a new {@link IdleHandler} to this message queue. This may be
+ * removed automatically for you by returning false from
+ * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
+ * invoked, or explicitly removing it with {@link #removeIdleHandler}.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @param handler The IdleHandler to be added.
+ */
+ public void addIdleHandler(@NonNull IdleHandler handler) {
+ if (handler == null) {
+ throw new NullPointerException("Can't add a null IdleHandler");
+ }
+ synchronized (mIdleHandlersLock) {
+ mIdleHandlers.add(handler);
+ }
+ }
+
+ /**
+ * Remove an {@link IdleHandler} from the queue that was previously added
+ * with {@link #addIdleHandler}. If the given object is not currently
+ * in the idle list, nothing is done.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @param handler The IdleHandler to be removed.
+ */
+ public void removeIdleHandler(@NonNull IdleHandler handler) {
+ synchronized (mIdleHandlersLock) {
+ mIdleHandlers.remove(handler);
+ }
+ }
+
+ /**
+ * Returns whether this looper's thread is currently polling for more work to do.
+ * This is a good signal that the loop is still alive rather than being stuck
+ * handling a callback. Note that this method is intrinsically racy, since the
+ * state of the loop can change before you get the result back.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @return True if the looper is currently polling for events.
+ * @hide
+ */
+ public boolean isPolling() {
+ // If the loop is quitting then it must not be idling.
+ // We can assume mPtr != 0 when sQuitting is false.
+ return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr);
+ }
+
+ /* Helper to choose the correct queue to insert into. */
+ private void insertIntoPriorityQueue(MessageNode msgNode) {
+ if (msgNode.isAsync()) {
+ mAsyncPriorityQueue.add(msgNode);
+ } else {
+ mPriorityQueue.add(msgNode);
+ }
+ }
+
+ private boolean removeFromPriorityQueue(MessageNode msgNode) {
+ if (msgNode.isAsync()) {
+ return mAsyncPriorityQueue.remove(msgNode);
+ } else {
+ return mPriorityQueue.remove(msgNode);
+ }
+ }
+
+ private MessageNode pickEarliestNode(MessageNode nodeA, MessageNode nodeB) {
+ if (nodeA != null && nodeB != null) {
+ if (nodeA.compareTo(nodeB) < 0) {
+ return nodeA;
+ }
+ return nodeB;
+ }
+
+ return nodeA != null ? nodeA : nodeB;
+ }
+
+ private MessageNode iterateNext(Iterator<MessageNode> iter) {
+ if (iter.hasNext()) {
+ try {
+ return iter.next();
+ } catch (NoSuchElementException e) {
+ /* The queue is empty - this can happen if we race with remove */
+ }
+ }
+ return null;
+ }
+
+ /* Move any non-cancelled messages into the priority queue */
+ private void drainStack(StackNode oldTop) {
+ while (oldTop.isMessageNode()) {
+ MessageNode oldTopMessageNode = (MessageNode) oldTop;
+ if (oldTopMessageNode.removeFromStack()) {
+ insertIntoPriorityQueue(oldTopMessageNode);
+ }
+ MessageNode inserted = oldTopMessageNode;
+ oldTop = oldTopMessageNode.mNext;
+ /*
+ * removeMessages can walk this list while we are consuming it.
+ * Set our next pointer to null *after* we add the message to our
+ * priority queue. This way removeMessages() will always find the
+ * message, either in our list or in the priority queue.
+ */
+ inserted.mNext = null;
+ }
+ }
+
+ /* Set the stack state to Active, return a list of nodes to walk. */
+ private StackNode swapAndSetStackStateActive() {
+ while (true) {
+ /* Set stack state to Active, get node list to walk later */
+ StackNode current = (StackNode) sState.getVolatile(this);
+ if (current == sStackStateActive
+ || sState.compareAndSet(this, current, sStackStateActive)) {
+ return current;
+ }
+ }
+ }
+
+ /* This is only read/written from the Looper thread */
+ private int mNextPollTimeoutMillis;
+ private static final AtomicLong mMessagesDelivered = new AtomicLong();
+
+ private Message nextMessage() {
+ int i = 0;
+
+ while (true) {
+ if (DEBUG) {
+ Log.d(TAG, "nextMessage loop #" + i);
+ i++;
+ }
+
+ mDrainingLock.lock();
+ mNextIsDrainingStack = true;
+ mDrainingLock.unlock();
+
+ /*
+ * Set our state to active, drain any items from the stack into our priority queues
+ */
+ StackNode oldTop;
+ oldTop = swapAndSetStackStateActive();
+ drainStack(oldTop);
+
+ mDrainingLock.lock();
+ mNextIsDrainingStack = false;
+ mDrainCompleted.signalAll();
+ mDrainingLock.unlock();
+
+ /*
+ * The objective of this next block of code is to:
+ * - find a message to return (if any is ready)
+ * - find a next message we would like to return, after scheduling.
+ * - we make our scheduling decision based on this next message (if it exists).
+ *
+ * We have two queues to juggle and the presence of barriers throws an additional
+ * wrench into our plans.
+ *
+ * The last wrinkle is that remove() may delete items from underneath us. If we hit
+ * that case, we simply restart the loop.
+ */
+
+ /* Get the first node from each queue */
+ Iterator<MessageNode> queueIter = mPriorityQueue.iterator();
+ MessageNode msgNode = iterateNext(queueIter);
+ Iterator<MessageNode> asyncQueueIter = mAsyncPriorityQueue.iterator();
+ MessageNode asyncMsgNode = iterateNext(asyncQueueIter);
+
+ if (DEBUG) {
+ if (msgNode != null) {
+ Message msg = msgNode.mMessage;
+ Log.d(TAG, "Next found node what: " + msg.what + " when: " + msg.when
+ + " seq: " + msgNode.mInsertSeq + "barrier: "
+ + msgNode.isBarrier() + " now: " + SystemClock.uptimeMillis());
+ }
+ if (asyncMsgNode != null) {
+ Message msg = asyncMsgNode.mMessage;
+ Log.d(TAG, "Next found async node what: " + msg.what + " when: " + msg.when
+ + " seq: " + asyncMsgNode.mInsertSeq + "barrier: "
+ + asyncMsgNode.isBarrier() + " now: "
+ + SystemClock.uptimeMillis());
+ }
+ }
+
+ /*
+ * the node which we will return, null if none are ready
+ */
+ MessageNode found = null;
+ /*
+ * The node from which we will determine our next wakeup time.
+ * Null indicates there is no next message ready. If we found a node,
+ * we can leave this null as Looper will call us again after delivering
+ * the message.
+ */
+ MessageNode next = null;
+
+ long now = SystemClock.uptimeMillis();
+ /*
+ * If we have a barrier we should return the async node (if it exists and is ready)
+ */
+ if (msgNode != null && msgNode.isBarrier()) {
+ if (asyncMsgNode != null && now >= asyncMsgNode.getWhen()) {
+ found = asyncMsgNode;
+ } else {
+ next = asyncMsgNode;
+ }
+ } else { /* No barrier. */
+ MessageNode earliest;
+ /*
+ * If we have two messages, pick the earliest option from either queue.
+ * Otherwise grab whichever node is non-null. If both are null we'll fall through.
+ */
+ earliest = pickEarliestNode(msgNode, asyncMsgNode);
+
+ if (earliest != null) {
+ if (now >= earliest.getWhen()) {
+ found = earliest;
+ } else {
+ next = earliest;
+ }
+ }
+ }
+
+ if (DEBUG) {
+ if (found != null) {
+ Message msg = found.mMessage;
+ Log.d(TAG, "Will deliver node what: " + msg.what + " when: " + msg.when
+ + " seq: " + found.mInsertSeq + " barrier: " + found.isBarrier()
+ + " async: " + found.isAsync() + " now: "
+ + SystemClock.uptimeMillis());
+ } else {
+ Log.d(TAG, "No node to deliver");
+ }
+ if (next != null) {
+ Message msg = next.mMessage;
+ Log.d(TAG, "Next node what: " + msg.what + " when: " + msg.when + " seq: "
+ + next.mInsertSeq + " barrier: " + next.isBarrier() + " async: "
+ + next.isAsync()
+ + " now: " + SystemClock.uptimeMillis());
+ } else {
+ Log.d(TAG, "No next node");
+ }
+ }
+
+ /*
+ * If we have a found message, we will get called again so there's no need to set state.
+ * In that case we can leave our state as ACTIVE.
+ *
+ * Otherwise we should determine how to park the thread.
+ */
+ StateNode nextOp = sStackStateActive;
+ if (found == null) {
+ if (next == null) {
+ /* No message to deliver, sleep indefinitely */
+ mNextPollTimeoutMillis = -1;
+ nextOp = sStackStateParked;
+ if (DEBUG) {
+ Log.d(TAG, "nextMessage next state is StackStateParked");
+ }
+ } else {
+ /* Message not ready, or we found one to deliver already, set a timeout */
+ long nextMessageWhen = next.getWhen();
+ if (nextMessageWhen > now) {
+ mNextPollTimeoutMillis = (int) Math.min(nextMessageWhen - now,
+ Integer.MAX_VALUE);
+ } else {
+ mNextPollTimeoutMillis = 0;
+ }
+
+ mStackStateTimedPark.mWhenToWake = now + mNextPollTimeoutMillis;
+ nextOp = mStackStateTimedPark;
+ if (DEBUG) {
+ Log.d(TAG, "nextMessage next state is StackStateTimedParked timeout ms "
+ + mNextPollTimeoutMillis + " mWhenToWake: "
+ + mStackStateTimedPark.mWhenToWake + " now " + now);
+ }
+ }
+ }
+
+ /*
+ * Try to swap our state from Active back to Park or TimedPark. If we raced with
+ * enqueue, loop back around to pick up any new items.
+ */
+ if (sState.compareAndSet(this, sStackStateActive, nextOp)) {
+ mMessageCounts.clearCounts();
+ if (found != null) {
+ if (!removeFromPriorityQueue(found)) {
+ /*
+ * RemoveMessages() might be able to pull messages out from under us
+ * However we can detect that here and just loop around if it happens.
+ */
+ continue;
+ }
+
+ if (TRACE) {
+ Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+ }
+ return found.mMessage;
+ }
+ return null;
+ }
+ }
+ }
+
+ Message next() {
+ final long ptr = mPtr;
+ if (ptr == 0) {
+ return null;
+ }
+
+ mNextPollTimeoutMillis = 0;
+ int pendingIdleHandlerCount = -1; // -1 only during first iteration
+ while (true) {
+ if (mNextPollTimeoutMillis != 0) {
+ Binder.flushPendingCommands();
+ }
+
+ nativePollOnce(ptr, mNextPollTimeoutMillis);
+
+ Message msg = nextMessage();
+ if (msg != null) {
+ msg.markInUse();
+ return msg;
+ }
+
+ if ((boolean) sQuitting.getVolatile(this)) {
+ return null;
+ }
+
+ synchronized (mIdleHandlersLock) {
+ // If first time idle, then get the number of idlers to run.
+ // Idle handles only run if the queue is empty or if the first message
+ // in the queue (possibly a barrier) is due to be handled in the future.
+ if (pendingIdleHandlerCount < 0
+ && mNextPollTimeoutMillis != 0) {
+ pendingIdleHandlerCount = mIdleHandlers.size();
+ }
+ if (pendingIdleHandlerCount <= 0) {
+ // No idle handlers to run. Loop and wait some more.
+ continue;
+ }
+
+ if (mPendingIdleHandlers == null) {
+ mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
+ }
+ mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
+ }
+
+ // Run the idle handlers.
+ // We only ever reach this code block during the first iteration.
+ for (int i = 0; i < pendingIdleHandlerCount; i++) {
+ final IdleHandler idler = mPendingIdleHandlers[i];
+ mPendingIdleHandlers[i] = null; // release the reference to the handler
+
+ boolean keep = false;
+ try {
+ keep = idler.queueIdle();
+ } catch (Throwable t) {
+ Log.wtf(TAG, "IdleHandler threw exception", t);
+ }
+
+ if (!keep) {
+ synchronized (mIdleHandlersLock) {
+ mIdleHandlers.remove(idler);
+ }
+ }
+ }
+
+ // Reset the idle handler count to 0 so we do not run them again.
+ pendingIdleHandlerCount = 0;
+
+ // While calling an idle handler, a new message could have been delivered
+ // so go back and look again for a pending message without waiting.
+ mNextPollTimeoutMillis = 0;
+ }
+ }
+
+ void quit(boolean safe) {
+ if (!mQuitAllowed) {
+ throw new IllegalStateException("Main thread not allowed to quit.");
+ }
+ synchronized (mIdleHandlersLock) {
+ if (sQuitting.compareAndSet(this, false, true)) {
+ if (safe) {
+ removeAllFutureMessages();
+ } else {
+ removeAllMessages();
+ }
+
+ // We can assume mPtr != 0 because sQuitting was previously false.
+ nativeWake(mPtr);
+ }
+ }
+ }
+
+ boolean enqueueMessage(@NonNull Message msg, long when) {
+ if (msg.target == null) {
+ throw new IllegalArgumentException("Message must have a target.");
+ }
+
+ if (msg.isInUse()) {
+ throw new IllegalStateException(msg + " This message is already in use.");
+ }
+
+ return enqueueMessageUnchecked(msg, when);
+ }
+
+ private boolean enqueueMessageUnchecked(@NonNull Message msg, long when) {
+ if ((boolean) sQuitting.getVolatile(this)) {
+ IllegalStateException e = new IllegalStateException(
+ msg.target + " sending message to a Handler on a dead thread");
+ Log.w(TAG, e.getMessage(), e);
+ msg.recycleUnchecked();
+ return false;
+ }
+
+ long seq = when != 0 ? ((long)sNextInsertSeq.getAndAdd(this, 1L) + 1L)
+ : ((long)sNextFrontInsertSeq.getAndAdd(this, -1L) - 1L);
+ /* TODO: Add a MessageNode member to Message so we can avoid this allocation */
+ MessageNode node = new MessageNode(msg, seq);
+ msg.when = when;
+ msg.markInUse();
+
+ if (DEBUG) {
+ Log.d(TAG, "Insert message what: " + msg.what + " when: " + msg.when + " seq: "
+ + node.mInsertSeq + " barrier: " + node.isBarrier() + " async: "
+ + node.isAsync() + " now: " + SystemClock.uptimeMillis());
+ }
+
+ while (true) {
+ StackNode old = (StackNode) sState.getVolatile(this);
+ boolean wakeNeeded;
+ boolean inactive;
+
+ node.mNext = old;
+ switch (old.getNodeType()) {
+ case STACK_NODE_ACTIVE:
+ /*
+ * The worker thread is currently active and will process any elements added to
+ * the stack before parking again.
+ */
+ node.mBottomOfStack = (StateNode) old;
+ inactive = false;
+ node.mWokeUp = true;
+ wakeNeeded = false;
+ break;
+
+ case STACK_NODE_PARKED:
+ node.mBottomOfStack = (StateNode) old;
+ inactive = true;
+ node.mWokeUp = true;
+ wakeNeeded = true;
+ break;
+
+ case STACK_NODE_TIMEDPARK:
+ node.mBottomOfStack = (StateNode) old;
+ inactive = true;
+ wakeNeeded = mStackStateTimedPark.mWhenToWake >= node.getWhen();
+ node.mWokeUp = wakeNeeded;
+ break;
+
+ default:
+ MessageNode oldMessage = (MessageNode) old;
+
+ node.mBottomOfStack = oldMessage.mBottomOfStack;
+ int bottomType = node.mBottomOfStack.getNodeType();
+ inactive = bottomType >= STACK_NODE_PARKED;
+ wakeNeeded = (bottomType == STACK_NODE_TIMEDPARK
+ && mStackStateTimedPark.mWhenToWake >= node.getWhen()
+ && !oldMessage.mWokeUp);
+ node.mWokeUp = oldMessage.mWokeUp || wakeNeeded;
+ break;
+ }
+ if (sState.compareAndSet(this, old, node)) {
+ if (inactive) {
+ if (wakeNeeded) {
+ nativeWake(mPtr);
+ } else {
+ mMessageCounts.incrementQueued();
+ }
+ }
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Posts a synchronization barrier to the Looper's message queue.
+ *
+ * Message processing occurs as usual until the message queue encounters the
+ * synchronization barrier that has been posted. When the barrier is encountered,
+ * later synchronous messages in the queue are stalled (prevented from being executed)
+ * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
+ * the token that identifies the synchronization barrier.
+ *
+ * This method is used to immediately postpone execution of all subsequently posted
+ * synchronous messages until a condition is met that releases the barrier.
+ * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
+ * and continue to be processed as usual.
+ *
+ * This call must be always matched by a call to {@link #removeSyncBarrier} with
+ * the same token to ensure that the message queue resumes normal operation.
+ * Otherwise the application will probably hang!
+ *
+ * @return A token that uniquely identifies the barrier. This token must be
+ * passed to {@link #removeSyncBarrier} to release the barrier.
+ *
+ * @hide
+ */
+ @TestApi
+ public int postSyncBarrier() {
+ return postSyncBarrier(SystemClock.uptimeMillis());
+ }
+
+ private int postSyncBarrier(long when) {
+ final int token = mNextBarrierToken.getAndIncrement();
+ final Message msg = Message.obtain();
+
+ msg.markInUse();
+ msg.arg1 = token;
+
+ if (!enqueueMessageUnchecked(msg, when)) {
+ Log.wtf(TAG, "Unexpected error while adding sync barrier!");
+ return -1;
+ }
+
+ return token;
+ }
+
+ private class MatchBarrierToken extends MessageCompare {
+ int mBarrierToken;
+
+ MatchBarrierToken(int token) {
+ super();
+ mBarrierToken = token;
+ }
+
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == null && m.arg1 == mBarrierToken) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Removes a synchronization barrier.
+ *
+ * @param token The synchronization barrier token that was returned by
+ * {@link #postSyncBarrier}.
+ *
+ * @throws IllegalStateException if the barrier was not found.
+ *
+ * @hide
+ */
+ @TestApi
+ public void removeSyncBarrier(int token) {
+ boolean removed;
+ MessageNode first;
+ final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token);
+
+ try {
+ /* Retain the first element to see if we are currently stuck on a barrier. */
+ first = mPriorityQueue.first();
+ } catch (NoSuchElementException e) {
+ /* The queue is empty */
+ first = null;
+ }
+
+ removed = findOrRemoveMessages(null, 0, null, null, 0, matchBarrierToken, true);
+ if (removed && first != null) {
+ Message m = first.mMessage;
+ if (m.target == null && m.arg1 == token) {
+ /* Wake up next() in case it was sleeping on this barrier. */
+ nativeWake(mPtr);
+ }
+ } else if (!removed) {
+ throw new IllegalStateException("The specified message queue synchronization "
+ + " barrier token has not been posted or has already been removed.");
+ }
+ }
+
+ private StateNode getStateNode(StackNode node) {
+ if (node.isMessageNode()) {
+ return ((MessageNode) node).mBottomOfStack;
+ }
+ return (StateNode) node;
+ }
+
+ private void waitForDrainCompleted() {
+ mDrainingLock.lock();
+ while (mNextIsDrainingStack) {
+ mDrainCompleted.awaitUninterruptibly();
+ }
+ mDrainingLock.unlock();
+ }
+
+ /*
+ * This class is used to find matches for hasMessages() and removeMessages()
+ */
+ private abstract static class MessageCompare {
+ public abstract boolean compareMessage(Message m, Handler h, int what, Object object,
+ Runnable r, long when);
+ }
+
+ private boolean stackHasMessages(Handler h, int what, Object object, Runnable r, long when,
+ MessageCompare compare, boolean removeMatches) {
+ boolean found = false;
+ StackNode top = (StackNode) sState.getVolatile(this);
+ StateNode bottom = getStateNode(top);
+
+ /*
+ * If the top node is a state node, there are no reachable messages.
+ * If it's anything other than Active, we can quit as we know that next() is not
+ * consuming items.
+ * If the top node is Active then we know that next() is currently consuming items.
+ * In that case we should wait next() has drained the stack.
+ */
+ if (top == bottom) {
+ if (bottom != sStackStateActive) {
+ return false;
+ }
+ waitForDrainCompleted();
+ return false;
+ }
+
+ /*
+ * We have messages that we may tombstone. Walk the stack until we hit the bottom or we
+ * hit a null pointer.
+ * If we hit the bottom, we are done.
+ * If we hit a null pointer, then the stack is being consumed by next() and we must cycle
+ * until the stack has been drained.
+ */
+ MessageNode p = (MessageNode) top;
+
+ while (true) {
+ if (compare.compareMessage(p.mMessage, h, what, object, r, when)) {
+ found = true;
+ if (DEBUG) {
+ Log.w(TAG, "stackHasMessages node matches");
+ }
+ if (removeMatches) {
+ if (p.removeFromStack()) {
+ p.mMessage.recycleUnchecked();
+ if (mMessageCounts.incrementCancelled()) {
+ nativeWake(mPtr);
+ }
+ }
+ } else {
+ return true;
+ }
+ }
+
+ StackNode n = p.mNext;
+ if (n == null) {
+ /* Next() is walking the stack, we must re-sample */
+ if (DEBUG) {
+ Log.d(TAG, "stackHasMessages next() is walking the stack, we must re-sample");
+ }
+ waitForDrainCompleted();
+ break;
+ }
+ if (!n.isMessageNode()) {
+ /* We reached the end of the stack */
+ return found;
+ }
+ p = (MessageNode) n;
+ }
+
+ return found;
+ }
+
+ private boolean priorityQueueHasMessage(ConcurrentSkipListSet<MessageNode> queue, Handler h,
+ int what, Object object, Runnable r, long when, MessageCompare compare,
+ boolean removeMatches) {
+ Iterator<MessageNode> iterator = queue.iterator();
+ boolean found = false;
+
+ while (iterator.hasNext()) {
+ MessageNode msg = iterator.next();
+
+ if (compare.compareMessage(msg.mMessage, h, what, object, r, when)) {
+ if (removeMatches) {
+ found = true;
+ if (queue.remove(msg)) {
+ msg.mMessage.recycleUnchecked();
+ }
+ } else {
+ return true;
+ }
+ }
+ }
+ return found;
+ }
+
+ private boolean findOrRemoveMessages(Handler h, int what, Object object, Runnable r, long when,
+ MessageCompare compare, boolean removeMatches) {
+ boolean foundInStack, foundInQueue;
+
+ foundInStack = stackHasMessages(h, what, object, r, when, compare, removeMatches);
+ foundInQueue = priorityQueueHasMessage(mPriorityQueue, h, what, object, r, when, compare,
+ removeMatches);
+ foundInQueue |= priorityQueueHasMessage(mAsyncPriorityQueue, h, what, object, r, when,
+ compare, removeMatches);
+
+ return foundInStack || foundInQueue;
+ }
+
+ private static class MatchHandlerWhatAndObject extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.what == what && (object == null || m.obj == object)) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerWhatAndObject mMatchHandlerWhatAndObject =
+ new MatchHandlerWhatAndObject();
+ boolean hasMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return false;
+ }
+
+ return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, false);
+ }
+
+ private static class MatchHandlerWhatAndObjectEquals extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerWhatAndObjectEquals mMatchHandlerWhatAndObjectEquals =
+ new MatchHandlerWhatAndObjectEquals();
+ boolean hasEqualMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return false;
+ }
+
+ return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals,
+ false);
+ }
+
+ private static class MatchHandlerRunnableAndObject extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.callback == r && (object == null || m.obj == object)) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerRunnableAndObject mMatchHandlerRunnableAndObject =
+ new MatchHandlerRunnableAndObject();
+
+ boolean hasMessages(Handler h, Runnable r, Object object) {
+ if (h == null) {
+ return false;
+ }
+
+ return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, false);
+ }
+
+ private static class MatchHandler extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandler mMatchHandler = new MatchHandler();
+ boolean hasMessages(Handler h) {
+ if (h == null) {
+ return false;
+ }
+ return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false);
+ }
+
+ void removeMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return;
+ }
+ findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true);
+ }
+
+ void removeEqualMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return;
+ }
+ findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true);
+ }
+
+ void removeMessages(Handler h, Runnable r, Object object) {
+ if (h == null || r == null) {
+ return;
+ }
+ findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true);
+ }
+
+ private static class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerRunnableAndObjectEquals mMatchHandlerRunnableAndObjectEquals =
+ new MatchHandlerRunnableAndObjectEquals();
+ void removeEqualMessages(Handler h, Runnable r, Object object) {
+ if (h == null || r == null) {
+ return;
+ }
+ findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true);
+ }
+
+ private static class MatchHandlerAndObject extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && (object == null || m.obj == object)) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerAndObject mMatchHandlerAndObject = new MatchHandlerAndObject();
+ void removeCallbacksAndMessages(Handler h, Object object) {
+ if (h == null) {
+ return;
+ }
+ findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true);
+ }
+
+ private static class MatchHandlerAndObjectEquals extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && (object == null || object.equals(m.obj))) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerAndObjectEquals mMatchHandlerAndObjectEquals =
+ new MatchHandlerAndObjectEquals();
+ void removeCallbacksAndEqualMessages(Handler h, Object object) {
+ if (h == null) {
+ return;
+ }
+ findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true);
+ }
+
+ private static class MatchAllMessages extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ return true;
+ }
+ }
+ private final MatchAllMessages mMatchAllMessages = new MatchAllMessages();
+ private void removeAllMessages() {
+ findOrRemoveMessages(null, -1, null, null, 0, mMatchAllMessages, true);
+ }
+
+ private static class MatchAllFutureMessages extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.when > when) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchAllFutureMessages mMatchAllFutureMessages = new MatchAllFutureMessages();
+ private void removeAllFutureMessages() {
+ findOrRemoveMessages(null, -1, null, null, SystemClock.uptimeMillis(),
+ mMatchAllFutureMessages, true);
+ }
+
+ private void printPriorityQueueNodes() {
+ Iterator<MessageNode> iterator = mPriorityQueue.iterator();
+
+ Log.d(TAG, "* Dump priority queue");
+ while (iterator.hasNext()) {
+ MessageNode msgNode = iterator.next();
+ Log.d(TAG, "** MessageNode what: " + msgNode.mMessage.what + " when "
+ + msgNode.mMessage.when + " seq: " + msgNode.mInsertSeq);
+ }
+ }
+
+ private int dumpPriorityQueue(ConcurrentSkipListSet<MessageNode> queue, Printer pw,
+ String prefix, Handler h, int n) {
+ int count = 0;
+ long now = SystemClock.uptimeMillis();
+
+ for (MessageNode msgNode : queue) {
+ Message msg = msgNode.mMessage;
+ if (h == null || h == msg.target) {
+ pw.println(prefix + "Message " + (n + count) + ": " + msg.toString(now));
+ }
+ count++;
+ }
+ return count;
+ }
+
+ void dump(Printer pw, String prefix, Handler h) {
+ long now = SystemClock.uptimeMillis();
+ int n = 0;
+
+ pw.println(prefix + "(MessageQueue is using Concurrent implementation)");
+
+ StackNode node = (StackNode) sState.getVolatile(this);
+ while (node != null) {
+ if (node.isMessageNode()) {
+ Message msg = ((MessageNode) node).mMessage;
+ if (h == null || h == msg.target) {
+ pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+ }
+ node = ((MessageNode) node).mNext;
+ } else {
+ pw.println(prefix + "State: " + node);
+ node = null;
+ }
+ n++;
+ }
+
+ pw.println(prefix + "PriorityQueue Messages: ");
+ n += dumpPriorityQueue(mPriorityQueue, pw, prefix, h, n);
+ pw.println(prefix + "AsyncPriorityQueue Messages: ");
+ n += dumpPriorityQueue(mAsyncPriorityQueue, pw, prefix, h, n);
+
+ pw.println(prefix + "(Total messages: " + n + ", polling=" + isPolling()
+ + ", quitting=" + (boolean) sQuitting.getVolatile(this) + ")");
+ }
+
+ private int dumpPriorityQueue(ConcurrentSkipListSet<MessageNode> queue,
+ ProtoOutputStream proto) {
+ int count = 0;
+
+ for (MessageNode msgNode : queue) {
+ Message msg = msgNode.mMessage;
+ msg.dumpDebug(proto, MessageQueueProto.MESSAGES);
+ count++;
+ }
+ return count;
+ }
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long messageQueueToken = proto.start(fieldId);
+
+ StackNode node = (StackNode) sState.getVolatile(this);
+ while (node.isMessageNode()) {
+ Message msg = ((MessageNode) node).mMessage;
+ msg.dumpDebug(proto, MessageQueueProto.MESSAGES);
+ node = ((MessageNode) node).mNext;
+ }
+
+ dumpPriorityQueue(mPriorityQueue, proto);
+ dumpPriorityQueue(mAsyncPriorityQueue, proto);
+
+ proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPolling());
+ proto.write(MessageQueueProto.IS_QUITTING, (boolean) sQuitting.getVolatile(this));
+ proto.end(messageQueueToken);
+ }
+
+ /**
+ * Adds a file descriptor listener to receive notification when file descriptor
+ * related events occur.
+ * <p>
+ * If the file descriptor has already been registered, the specified events
+ * and listener will replace any that were previously associated with it.
+ * It is not possible to set more than one listener per file descriptor.
+ * </p><p>
+ * It is important to always unregister the listener when the file descriptor
+ * is no longer of use.
+ * </p>
+ *
+ * @param fd The file descriptor for which a listener will be registered.
+ * @param events The set of events to receive: a combination of the
+ * {@link OnFileDescriptorEventListener#EVENT_INPUT},
+ * {@link OnFileDescriptorEventListener#EVENT_OUTPUT}, and
+ * {@link OnFileDescriptorEventListener#EVENT_ERROR} event masks. If the requested
+ * set of events is zero, then the listener is unregistered.
+ * @param listener The listener to invoke when file descriptor events occur.
+ *
+ * @see OnFileDescriptorEventListener
+ * @see #removeOnFileDescriptorEventListener
+ */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,
+ @OnFileDescriptorEventListener.Events int events,
+ @NonNull OnFileDescriptorEventListener listener) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mFileDescriptorRecordsLock) {
+ updateOnFileDescriptorEventListenerLocked(fd, events, listener);
+ }
+ }
+
+ /**
+ * Removes a file descriptor listener.
+ * <p>
+ * This method does nothing if no listener has been registered for the
+ * specified file descriptor.
+ * </p>
+ *
+ * @param fd The file descriptor whose listener will be unregistered.
+ *
+ * @see OnFileDescriptorEventListener
+ * @see #addOnFileDescriptorEventListener
+ */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+
+ synchronized (mFileDescriptorRecordsLock) {
+ updateOnFileDescriptorEventListenerLocked(fd, 0, null);
+ }
+ }
+
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,
+ OnFileDescriptorEventListener listener) {
+ final int fdNum = fd.getInt$();
+
+ int index = -1;
+ FileDescriptorRecord record = null;
+ if (mFileDescriptorRecords != null) {
+ index = mFileDescriptorRecords.indexOfKey(fdNum);
+ if (index >= 0) {
+ record = mFileDescriptorRecords.valueAt(index);
+ if (record != null && record.mEvents == events) {
+ return;
+ }
+ }
+ }
+
+ if (events != 0) {
+ events |= OnFileDescriptorEventListener.EVENT_ERROR;
+ if (record == null) {
+ if (mFileDescriptorRecords == null) {
+ mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>();
+ }
+ record = new FileDescriptorRecord(fd, events, listener);
+ mFileDescriptorRecords.put(fdNum, record);
+ } else {
+ record.mListener = listener;
+ record.mEvents = events;
+ record.mSeq += 1;
+ }
+ nativeSetFileDescriptorEvents(mPtr, fdNum, events);
+ } else if (record != null) {
+ record.mEvents = 0;
+ mFileDescriptorRecords.removeAt(index);
+ nativeSetFileDescriptorEvents(mPtr, fdNum, 0);
+ }
+ }
+
+ // Called from native code.
+ private int dispatchEvents(int fd, int events) {
+ // Get the file descriptor record and any state that might change.
+ final FileDescriptorRecord record;
+ final int oldWatchedEvents;
+ final OnFileDescriptorEventListener listener;
+ final int seq;
+ synchronized (mFileDescriptorRecordsLock) {
+ record = mFileDescriptorRecords.get(fd);
+ if (record == null) {
+ return 0; // spurious, no listener registered
+ }
+
+ oldWatchedEvents = record.mEvents;
+ events &= oldWatchedEvents; // filter events based on current watched set
+ if (events == 0) {
+ return oldWatchedEvents; // spurious, watched events changed
+ }
+
+ listener = record.mListener;
+ seq = record.mSeq;
+ }
+
+ // Invoke the listener outside of the lock.
+ int newWatchedEvents = listener.onFileDescriptorEvents(
+ record.mDescriptor, events);
+ if (newWatchedEvents != 0) {
+ newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR;
+ }
+
+ // Update the file descriptor record if the listener changed the set of
+ // events to watch and the listener itself hasn't been updated since.
+ if (newWatchedEvents != oldWatchedEvents) {
+ synchronized (mFileDescriptorRecordsLock) {
+ int index = mFileDescriptorRecords.indexOfKey(fd);
+ if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
+ && record.mSeq == seq) {
+ record.mEvents = newWatchedEvents;
+ if (newWatchedEvents == 0) {
+ mFileDescriptorRecords.removeAt(index);
+ }
+ }
+ }
+ }
+
+ // Return the new set of events to watch for native code to take care of.
+ return newWatchedEvents;
+ }
+
+ /**
+ * Callback interface for discovering when a thread is going to block
+ * waiting for more messages.
+ */
+ public static interface IdleHandler {
+ /**
+ * Called when the message queue has run out of messages and will now
+ * wait for more. Return true to keep your idle handler active, false
+ * to have it removed. This may be called if there are still messages
+ * pending in the queue, but they are all scheduled to be dispatched
+ * after the current time.
+ */
+ boolean queueIdle();
+ }
+
+ /**
+ * A listener which is invoked when file descriptor related events occur.
+ */
+ public interface OnFileDescriptorEventListener {
+ /**
+ * File descriptor event: Indicates that the file descriptor is ready for input
+ * operations, such as reading.
+ * <p>
+ * The listener should read all available data from the file descriptor
+ * then return <code>true</code> to keep the listener active or <code>false</code>
+ * to remove the listener.
+ * </p><p>
+ * In the case of a socket, this event may be generated to indicate
+ * that there is at least one incoming connection that the listener
+ * should accept.
+ * </p><p>
+ * This event will only be generated if the {@link #EVENT_INPUT} event mask was
+ * specified when the listener was added.
+ * </p>
+ */
+ public static final int EVENT_INPUT = 1 << 0;
+
+ /**
+ * File descriptor event: Indicates that the file descriptor is ready for output
+ * operations, such as writing.
+ * <p>
+ * The listener should write as much data as it needs. If it could not
+ * write everything at once, then it should return <code>true</code> to
+ * keep the listener active. Otherwise, it should return <code>false</code>
+ * to remove the listener then re-register it later when it needs to write
+ * something else.
+ * </p><p>
+ * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was
+ * specified when the listener was added.
+ * </p>
+ */
+ public static final int EVENT_OUTPUT = 1 << 1;
+
+ /**
+ * File descriptor event: Indicates that the file descriptor encountered a
+ * fatal error.
+ * <p>
+ * File descriptor errors can occur for various reasons. One common error
+ * is when the remote peer of a socket or pipe closes its end of the connection.
+ * </p><p>
+ * This event may be generated at any time regardless of whether the
+ * {@link #EVENT_ERROR} event mask was specified when the listener was added.
+ * </p>
+ */
+ public static final int EVENT_ERROR = 1 << 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "EVENT_" }, value = {
+ EVENT_INPUT,
+ EVENT_OUTPUT,
+ EVENT_ERROR
+ })
+ public @interface Events {}
+
+ /**
+ * Called when a file descriptor receives events.
+ *
+ * @param fd The file descriptor.
+ * @param events The set of events that occurred: a combination of the
+ * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks.
+ * @return The new set of events to watch, or 0 to unregister the listener.
+ *
+ * @see #EVENT_INPUT
+ * @see #EVENT_OUTPUT
+ * @see #EVENT_ERROR
+ */
+ @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events);
+ }
+
+ static final class FileDescriptorRecord {
+ public final FileDescriptor mDescriptor;
+ public int mEvents;
+ public OnFileDescriptorEventListener mListener;
+ public int mSeq;
+
+ public FileDescriptorRecord(FileDescriptor descriptor,
+ int events, OnFileDescriptorEventListener listener) {
+ mDescriptor = descriptor;
+ mEvents = events;
+ mListener = listener;
+ }
+ }
+}
diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
similarity index 97%
rename from core/java/android/os/MessageQueue.java
rename to core/java/android/os/LegacyMessageQueue/MessageQueue.java
index 5b711c9..6b9b349 100644
--- a/core/java/android/os/MessageQueue.java
+++ b/core/java/android/os/LegacyMessageQueue/MessageQueue.java
@@ -20,6 +20,9 @@
import android.annotation.NonNull;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Handler;
+import android.os.Process;
+import android.os.Trace;
import android.util.Log;
import android.util.Printer;
import android.util.SparseArray;
@@ -29,6 +32,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Low-level class holding the list of messages to be dispatched by a
@@ -44,6 +48,7 @@
public final class MessageQueue {
private static final String TAG = "MessageQueue";
private static final boolean DEBUG = false;
+ private static final boolean TRACE = false;
// True if the message queue can be quit.
@UnsupportedAppUsage
@@ -326,6 +331,8 @@
return newWatchedEvents;
}
+ private static final AtomicLong mMessagesDelivered = new AtomicLong();
+
@UnsupportedAppUsage
Message next() {
// Return here if the message loop has already quit and been disposed.
@@ -381,6 +388,9 @@
if (msg.isAsynchronous()) {
mAsyncMessageCount--;
}
+ if (TRACE) {
+ Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+ }
return msg;
}
} else {
@@ -794,7 +804,7 @@
Message n = p.next;
if (n != null) {
if (n.target == h && n.what == what
- && (object == null || n.obj == object)) {
+ && (object == null || n.obj == object)) {
Message nn = n.next;
if (n.isAsynchronous()) {
mAsyncMessageCount--;
@@ -841,7 +851,7 @@
Message n = p.next;
if (n != null) {
if (n.target == h && n.what == what
- && (object == null || object.equals(n.obj))) {
+ && (object == null || object.equals(n.obj))) {
Message nn = n.next;
if (n.isAsynchronous()) {
mAsyncMessageCount--;
@@ -888,7 +898,7 @@
Message n = p.next;
if (n != null) {
if (n.target == h && n.callback == r
- && (object == null || n.obj == object)) {
+ && (object == null || n.obj == object)) {
Message nn = n.next;
if (n.isAsynchronous()) {
mAsyncMessageCount--;
@@ -935,7 +945,7 @@
Message n = p.next;
if (n != null) {
if (n.target == h && n.callback == r
- && (object == null || object.equals(n.obj))) {
+ && (object == null || object.equals(n.obj))) {
Message nn = n.next;
if (n.isAsynchronous()) {
mAsyncMessageCount--;
@@ -1093,6 +1103,7 @@
void dump(Printer pw, String prefix, Handler h) {
synchronized (this) {
+ pw.println(prefix + "(MessageQueue is using Legacy implementation)");
long now = SystemClock.uptimeMillis();
int n = 0;
for (Message msg = mMessages; msg != null; msg = msg.next) {
diff --git a/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java
new file mode 100644
index 0000000..967332f
--- /dev/null
+++ b/core/java/android/os/SemiConcurrentMessageQueue/MessageQueue.java
@@ -0,0 +1,1589 @@
+/*
+ * 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 android.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Handler;
+import android.os.Trace;
+import android.util.Log;
+import android.util.Printer;
+import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.PriorityQueue;
+import java.util.PriorityQueue;
+import java.util.PriorityQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Low-level class holding the list of messages to be dispatched by a
+ * {@link Looper}. Messages are not added directly to a MessageQueue,
+ * but rather through {@link Handler} objects associated with the Looper.
+ *
+ * <p>You can retrieve the MessageQueue for the current thread with
+ * {@link Looper#myQueue() Looper.myQueue()}.
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass(
+ "com.android.platform.test.ravenwood.nativesubstitution.MessageQueue_host")
+public final class MessageQueue {
+ private static final String TAG = "SemiConcurrentMessageQueue";
+ private static final boolean DEBUG = false;
+ private static final boolean TRACE = false;
+
+ // True if the message queue can be quit.
+ private final boolean mQuitAllowed;
+
+ @SuppressWarnings("unused")
+ private long mPtr; // used by native code
+
+ @IntDef(value = {
+ STACK_NODE_MESSAGE,
+ STACK_NODE_ACTIVE,
+ STACK_NODE_PARKED,
+ STACK_NODE_TIMEDPARK})
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface StackNodeType {}
+
+ /*
+ * Stack node types. STACK_NODE_MESSAGE indicates a node containing a message.
+ * The other types indicate what state our Looper thread is in. The bottom of
+ * the stack is always a single state node. Message nodes are added on top.
+ */
+ private static final int STACK_NODE_MESSAGE = 0;
+ /*
+ * Active state indicates that next() is processing messages
+ */
+ private static final int STACK_NODE_ACTIVE = 1;
+ /*
+ * Parked state indicates that the Looper thread is sleeping indefinitely (nothing to deliver)
+ */
+ private static final int STACK_NODE_PARKED = 2;
+ /*
+ * Timed Park state indicates that the Looper thread is sleeping, waiting for a message
+ * deadline
+ */
+ private static final int STACK_NODE_TIMEDPARK = 3;
+
+ /* Describes a node in the Treiber stack */
+ static class StackNode {
+ @StackNodeType
+ private final int mType;
+
+ StackNode(@StackNodeType int type) {
+ mType = type;
+ }
+
+ @StackNodeType
+ final int getNodeType() {
+ return mType;
+ }
+
+ final boolean isMessageNode() {
+ return mType == STACK_NODE_MESSAGE;
+ }
+ }
+
+ static final class MessageNode extends StackNode implements Comparable<MessageNode> {
+ private final Message mMessage;
+ volatile StackNode mNext;
+ StateNode mBottomOfStack;
+ boolean mWokeUp;
+ boolean mRemovedFromStack = false;
+ final long mInsertSeq;
+
+ MessageNode(@NonNull Message message, long insertSeq) {
+ super(STACK_NODE_MESSAGE);
+ mMessage = message;
+ mInsertSeq = insertSeq;
+ }
+
+ long getWhen() {
+ return mMessage.when;
+ }
+
+ boolean isRemovedFromStack() {
+ return mRemovedFromStack;
+ }
+
+ boolean removeFromStack() {
+ if (!mRemovedFromStack) {
+ mRemovedFromStack = true;
+ return true;
+ }
+ return false;
+ }
+
+ boolean isAsync() {
+ return mMessage.isAsynchronous();
+ }
+
+ boolean isBarrier() {
+ return mMessage.target == null;
+ }
+
+ @Override
+ public int compareTo(@NonNull MessageNode messageNode) {
+ Message other = messageNode.mMessage;
+
+ int compared = Long.compare(mMessage.when, other.when);
+ if (compared == 0) {
+ compared = Long.compare(mInsertSeq, messageNode.mInsertSeq);
+ }
+ return compared;
+ }
+ }
+
+ static class StateNode extends StackNode {
+ StateNode(int type) {
+ super(type);
+ }
+ }
+
+ static final class TimedParkStateNode extends StateNode {
+ long mWhenToWake;
+
+ TimedParkStateNode() {
+ super(STACK_NODE_TIMEDPARK);
+ }
+ }
+
+ private static final StateNode sStackStateActive = new StateNode(STACK_NODE_ACTIVE);
+ private static final StateNode sStackStateParked = new StateNode(STACK_NODE_PARKED);
+ private final TimedParkStateNode mStackStateTimedPark = new TimedParkStateNode();
+
+ /* This is the top of our treiber stack. */
+ private static final VarHandle sState;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sState = l.findVarHandle(MessageQueue.class, "mStateValue",
+ MessageQueue.StackNode.class);
+ } catch (Exception e) {
+ Log.wtf(TAG, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ private volatile StackNode mStateValue = sStackStateParked;
+ @GuardedBy("mPriorityQueue")
+ private final PriorityQueue<MessageNode> mPriorityQueue =
+ new PriorityQueue<MessageNode>();
+ @GuardedBy("mPriorityQueue")
+ private final PriorityQueue<MessageNode> mAsyncPriorityQueue =
+ new PriorityQueue<MessageNode>();
+
+ /*
+ * This helps us ensure that messages with the same timestamp are inserted in FIFO order.
+ * Increments on each insert, starting at 0. MessageNode.compareTo() will compare sequences
+ * when delivery timestamps are identical.
+ */
+ private static final VarHandle sNextInsertSeq;
+ private volatile long mNextInsertSeqValue = 0;
+ /*
+ * The exception to the FIFO order rule is sendMessageAtFrontOfQueue().
+ * Those messages must be in LIFO order - SIGH.
+ * Decrements on each front of queue insert.
+ */
+ private static final VarHandle sNextFrontInsertSeq;
+ private volatile long mNextFrontInsertSeqValue = -1;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sNextInsertSeq = l.findVarHandle(MessageQueue.class, "mNextInsertSeqValue",
+ long.class);
+ sNextFrontInsertSeq = l.findVarHandle(MessageQueue.class, "mNextFrontInsertSeqValue",
+ long.class);
+ } catch (Exception e) {
+ Log.wtf(TAG, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+
+ }
+
+ /*
+ * Tracks the number of queued and cancelled messages in our stack.
+ *
+ * On item cancellation, determine whether to wake next() to flush tombstoned messages.
+ * We track queued and cancelled counts as two ints packed into a single long.
+ */
+ private static final class MessageCounts {
+ private static VarHandle sCounts;
+ private volatile long mCountsValue = 0;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sCounts = l.findVarHandle(MessageQueue.MessageCounts.class, "mCountsValue",
+ long.class);
+ } catch (Exception e) {
+ Log.wtf(TAG, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+ /* We use a special value to indicate when next() has been woken for flush. */
+ private static final long AWAKE = Long.MAX_VALUE;
+ /*
+ * Minimum number of messages in the stack which we need before we consider flushing
+ * tombstoned items.
+ */
+ private static final int MESSAGE_FLUSH_THRESHOLD = 10;
+
+ private static int numQueued(long val) {
+ return (int) (val >>> Integer.SIZE);
+ }
+
+ private static int numCancelled(long val) {
+ return (int) val;
+ }
+
+ private static long combineCounts(int queued, int cancelled) {
+ return ((long) queued << Integer.SIZE) | (long) cancelled;
+ }
+
+ public void incrementQueued() {
+ while (true) {
+ long oldVal = mCountsValue;
+ int queued = numQueued(oldVal);
+ int cancelled = numCancelled(oldVal);
+ /* Use Math.max() to avoid overflow of queued count */
+ long newVal = combineCounts(Math.max(queued + 1, queued), cancelled);
+
+ /* Don't overwrite 'AWAKE' state */
+ if (oldVal == AWAKE || sCounts.compareAndSet(this, oldVal, newVal)) {
+ break;
+ }
+ }
+ }
+
+ public boolean incrementCancelled() {
+ while (true) {
+ long oldVal = mCountsValue;
+ if (oldVal == AWAKE) {
+ return false;
+ }
+ int queued = numQueued(oldVal);
+ int cancelled = numCancelled(oldVal);
+ boolean needsPurge = queued > MESSAGE_FLUSH_THRESHOLD
+ && (queued >> 1) < cancelled;
+ long newVal;
+ if (needsPurge) {
+ newVal = AWAKE;
+ } else {
+ newVal = combineCounts(queued,
+ Math.max(cancelled + 1, cancelled));
+ }
+
+ if (sCounts.compareAndSet(this, oldVal, newVal)) {
+ return needsPurge;
+ }
+ }
+ }
+
+ public void clearCounts() {
+ mCountsValue = 0;
+ }
+ }
+
+ private final MessageCounts mMessageCounts = new MessageCounts();
+
+ private final Object mIdleHandlersLock = new Object();
+ @GuardedBy("mIdleHandlersLock")
+ private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>();
+ private IdleHandler[] mPendingIdleHandlers;
+
+ private final Object mFileDescriptorRecordsLock = new Object();
+ @GuardedBy("mFileDescriptorRecordsLock")
+ private SparseArray<FileDescriptorRecord> mFileDescriptorRecords;
+
+ private static final VarHandle sQuitting;
+ private boolean mQuittingValue = false;
+ static {
+ try {
+ MethodHandles.Lookup l = MethodHandles.lookup();
+ sQuitting = l.findVarHandle(MessageQueue.class, "mQuittingValue", boolean.class);
+ } catch (Exception e) {
+ Log.wtf(TAG, "VarHandle lookup failed with exception: " + e);
+ throw new ExceptionInInitializerError(e);
+ }
+ }
+
+ // The next barrier token.
+ // Barriers are indicated by messages with a null target whose arg1 field carries the token.
+ private final AtomicInteger mNextBarrierToken = new AtomicInteger(1);
+
+ private static native long nativeInit();
+ private static native void nativeDestroy(long ptr);
+ private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
+ private static native void nativeWake(long ptr);
+ private static native boolean nativeIsPolling(long ptr);
+ private static native void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
+
+ MessageQueue(boolean quitAllowed) {
+ mQuitAllowed = quitAllowed;
+ mPtr = nativeInit();
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ // Disposes of the underlying message queue.
+ // Must only be called on the looper thread or the finalizer.
+ private void dispose() {
+ if (mPtr != 0) {
+ nativeDestroy(mPtr);
+ mPtr = 0;
+ }
+ }
+
+ /**
+ * Returns true if the looper has no pending messages which are due to be processed.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @return True if the looper is idle.
+ */
+ public boolean isIdle() {
+ MessageNode msgNode = null;
+ MessageNode asyncMsgNode = null;
+
+ synchronized (mPriorityQueue) {
+ msgNode = mPriorityQueue.peek();
+ asyncMsgNode = mAsyncPriorityQueue.peek();
+
+ final long now = SystemClock.uptimeMillis();
+ if ((msgNode != null && msgNode.getWhen() <= now)
+ || (asyncMsgNode != null && asyncMsgNode.getWhen() <= now)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Add a new {@link IdleHandler} to this message queue. This may be
+ * removed automatically for you by returning false from
+ * {@link IdleHandler#queueIdle IdleHandler.queueIdle()} when it is
+ * invoked, or explicitly removing it with {@link #removeIdleHandler}.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @param handler The IdleHandler to be added.
+ */
+ public void addIdleHandler(@NonNull IdleHandler handler) {
+ if (handler == null) {
+ throw new NullPointerException("Can't add a null IdleHandler");
+ }
+ synchronized (mIdleHandlersLock) {
+ mIdleHandlers.add(handler);
+ }
+ }
+
+ /**
+ * Remove an {@link IdleHandler} from the queue that was previously added
+ * with {@link #addIdleHandler}. If the given object is not currently
+ * in the idle list, nothing is done.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @param handler The IdleHandler to be removed.
+ */
+ public void removeIdleHandler(@NonNull IdleHandler handler) {
+ synchronized (mIdleHandlersLock) {
+ mIdleHandlers.remove(handler);
+ }
+ }
+
+ /**
+ * Returns whether this looper's thread is currently polling for more work to do.
+ * This is a good signal that the loop is still alive rather than being stuck
+ * handling a callback. Note that this method is intrinsically racy, since the
+ * state of the loop can change before you get the result back.
+ *
+ * <p>This method is safe to call from any thread.
+ *
+ * @return True if the looper is currently polling for events.
+ * @hide
+ */
+ public boolean isPolling() {
+ // If the loop is quitting then it must not be idling.
+ // We can assume mPtr != 0 when sQuitting is false.
+ return !((boolean) sQuitting.getVolatile(this)) && nativeIsPolling(mPtr);
+ }
+
+ /* Helper to choose the correct queue to insert into. */
+ @GuardedBy("mPriorityQueue")
+ private void insertIntoPriorityQueue(MessageNode msgNode) {
+ if (msgNode.isAsync()) {
+ mAsyncPriorityQueue.offer(msgNode);
+ } else {
+ mPriorityQueue.offer(msgNode);
+ }
+ }
+
+ @GuardedBy("mPriorityQueue")
+ private boolean removeFromPriorityQueue(MessageNode msgNode) {
+ if (msgNode.isAsync()) {
+ return mAsyncPriorityQueue.remove(msgNode);
+ } else {
+ return mPriorityQueue.remove(msgNode);
+ }
+ }
+
+ private MessageNode pickEarliestNode(MessageNode nodeA, MessageNode nodeB) {
+ if (nodeA != null && nodeB != null) {
+ if (nodeA.compareTo(nodeB) < 0) {
+ return nodeA;
+ }
+ return nodeB;
+ }
+
+ return nodeA != null ? nodeA : nodeB;
+ }
+
+ /* Move any non-cancelled messages into the priority queue */
+ private void drainStack(StackNode oldTop) {
+ while (oldTop.isMessageNode()) {
+ MessageNode oldTopMessageNode = (MessageNode) oldTop;
+ if (oldTopMessageNode.removeFromStack()) {
+ insertIntoPriorityQueue(oldTopMessageNode);
+ }
+ MessageNode inserted = oldTopMessageNode;
+ oldTop = oldTopMessageNode.mNext;
+ }
+ }
+
+ /* Set the stack state to Active, return a list of nodes to walk. */
+ private StackNode swapAndSetStackStateActive() {
+ while (true) {
+ /* Set stack state to Active, get node list to walk later */
+ StackNode current = (StackNode) sState.getVolatile(this);
+ if (current == sStackStateActive
+ || sState.compareAndSet(this, current, sStackStateActive)) {
+ return current;
+ }
+ }
+ }
+
+ /* This is only read/written from the Looper thread */
+ private int mNextPollTimeoutMillis;
+ private static final AtomicLong mMessagesDelivered = new AtomicLong();
+
+ private Message nextMessage() {
+ int i = 0;
+
+ while (true) {
+ if (DEBUG) {
+ Log.d(TAG, "nextMessage loop #" + i);
+ i++;
+ }
+
+ /* This protects us from racing with remove. Enqueue can still add items. */
+ synchronized (mPriorityQueue) {
+
+ /*
+ * Set our state to active, drain any items from the stack into our priority queues
+ */
+ StackNode oldTop;
+ oldTop = swapAndSetStackStateActive();
+ drainStack(oldTop);
+
+ /*
+ * The objective of this next block of code is to:
+ * - find a message to return (if any is ready)
+ * - find a next message we would like to return, after scheduling.
+ * - we make our scheduling decision based on this next message (if it exists).
+ *
+ * We have two queues to juggle and the presence of barriers throws an additional
+ * wrench into our plans.
+ */
+
+ /* Get the first node from each queue */
+ MessageNode msgNode = mPriorityQueue.peek();
+ MessageNode asyncMsgNode = mAsyncPriorityQueue.peek();
+
+ if (DEBUG) {
+ if (msgNode != null) {
+ Message msg = msgNode.mMessage;
+ Log.d(TAG, "Next found node what: " + msg.what + " when: " + msg.when
+ + " seq: " + msgNode.mInsertSeq + "barrier: "
+ + msgNode.isBarrier() + " now: "
+ + SystemClock.uptimeMillis());
+ }
+ if (asyncMsgNode != null) {
+ Message msg = asyncMsgNode.mMessage;
+ Log.d(TAG, "Next found async node what: " + msg.what + " when: " + msg.when
+ + " seq: " + asyncMsgNode.mInsertSeq + "barrier: "
+ + asyncMsgNode.isBarrier() + " now: "
+ + SystemClock.uptimeMillis());
+ }
+ }
+
+ /*
+ * the node which we will return, null if none are ready
+ */
+ MessageNode found = null;
+ /*
+ * The node from which we will determine our next wakeup time.
+ * Null indicates there is no next message ready. If we found a node,
+ * we can leave this null as Looper will call us again after delivering
+ * the message.
+ */
+ MessageNode next = null;
+
+ long now = SystemClock.uptimeMillis();
+ /*
+ * If we have a barrier we should return the async node if it exists and is
+ * ready
+ */
+ if (msgNode != null && msgNode.isBarrier()) {
+ if (asyncMsgNode != null && now >= asyncMsgNode.getWhen()) {
+ found = asyncMsgNode;
+ removeFromPriorityQueue(found);
+ } else {
+ next = asyncMsgNode;
+ }
+ } else { /* No barrier. */
+ MessageNode earliest;
+ /*
+ * If we have two messages, pick the earliest option from either queue.
+ * Otherwise grab whichever node is non-null. If both are null we'll fall
+ * through.
+ */
+ earliest = pickEarliestNode(msgNode, asyncMsgNode);
+
+ if (earliest != null) {
+ if (now >= earliest.getWhen()) {
+ found = earliest;
+ removeFromPriorityQueue(found);
+ } else {
+ next = earliest;
+ }
+ }
+ }
+
+ if (DEBUG) {
+ if (found != null) {
+ Message msg = found.mMessage;
+ Log.d(TAG, "Will deliver node what: " + msg.what + " when: " + msg.when
+ + " seq: " + found.mInsertSeq + " barrier: "
+ + found.isBarrier() + " async: " + found.isAsync()
+ + " now: " + SystemClock.uptimeMillis());
+ } else {
+ Log.d(TAG, "No node to deliver");
+ }
+ if (next != null) {
+ Message msg = next.mMessage;
+ Log.d(TAG, "Next node what: " + msg.what + " when: " + msg.when + " seq: "
+ + next.mInsertSeq + " barrier: " + next.isBarrier()
+ + " async: " + next.isAsync()
+ + " now: " + SystemClock.uptimeMillis());
+ } else {
+ Log.d(TAG, "No next node");
+ }
+ }
+
+ /*
+ * If we have a found message, we will get called again so there's no need to set
+ * state.
+ * In that case we can leave our state as ACTIVE.
+ *
+ * Otherwise we should determine how to park the thread.
+ */
+ StateNode nextOp = sStackStateActive;
+ if (found == null) {
+ if (next == null) {
+ /* No message to deliver, sleep indefinitely */
+ mNextPollTimeoutMillis = -1;
+ nextOp = sStackStateParked;
+ if (DEBUG) {
+ Log.d(TAG, "nextMessage next state is StackStateParked");
+ }
+ } else {
+ /* Message not ready, or we found one to deliver already, set a timeout */
+ long nextMessageWhen = next.getWhen();
+ if (nextMessageWhen > now) {
+ mNextPollTimeoutMillis = (int) Math.min(nextMessageWhen - now,
+ Integer.MAX_VALUE);
+ } else {
+ mNextPollTimeoutMillis = 0;
+ }
+
+ mStackStateTimedPark.mWhenToWake = now + mNextPollTimeoutMillis;
+ nextOp = mStackStateTimedPark;
+ if (DEBUG) {
+ Log.d(TAG, "nextMessage next state is StackStateTimedParked "
+ + " next timeout ms " + mNextPollTimeoutMillis
+ + " mWhenToWake: " + mStackStateTimedPark.mWhenToWake
+ + " now " + now);
+ }
+ }
+ }
+
+ /*
+ * Try to swap our state from Active back to Park or TimedPark. If we raced with
+ * enqueue, loop back around to pick up any new items.
+ */
+ if (sState.compareAndSet(this, sStackStateActive, nextOp)) {
+ mMessageCounts.clearCounts();
+ if (found != null) {
+ if (TRACE) {
+ Trace.setCounter("MQ.Delivered", mMessagesDelivered.incrementAndGet());
+ }
+ return found.mMessage;
+ }
+ return null;
+ }
+ if (found != null) {
+ /*
+ * Add this node back - we will be adding new nodes into our priority queue, and
+ * recalculating what to return.
+ */
+ insertIntoPriorityQueue(found);
+ }
+ }
+ }
+ }
+
+ Message next() {
+ final long ptr = mPtr;
+ if (ptr == 0) {
+ return null;
+ }
+
+ mNextPollTimeoutMillis = 0;
+ int pendingIdleHandlerCount = -1; // -1 only during first iteration
+ while (true) {
+ if (mNextPollTimeoutMillis != 0) {
+ Binder.flushPendingCommands();
+ }
+
+ nativePollOnce(ptr, mNextPollTimeoutMillis);
+
+ Message msg = nextMessage();
+ if (msg != null) {
+ msg.markInUse();
+ return msg;
+ }
+
+ if ((boolean) sQuitting.getVolatile(this)) {
+ return null;
+ }
+
+ synchronized (mIdleHandlersLock) {
+ // If first time idle, then get the number of idlers to run.
+ // Idle handles only run if the queue is empty or if the first message
+ // in the queue (possibly a barrier) is due to be handled in the future.
+ if (pendingIdleHandlerCount < 0
+ && mNextPollTimeoutMillis != 0) {
+ pendingIdleHandlerCount = mIdleHandlers.size();
+ }
+ if (pendingIdleHandlerCount <= 0) {
+ // No idle handlers to run. Loop and wait some more.
+ continue;
+ }
+
+ if (mPendingIdleHandlers == null) {
+ mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
+ }
+ mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
+ }
+
+ // Run the idle handlers.
+ // We only ever reach this code block during the first iteration.
+ for (int i = 0; i < pendingIdleHandlerCount; i++) {
+ final IdleHandler idler = mPendingIdleHandlers[i];
+ mPendingIdleHandlers[i] = null; // release the reference to the handler
+
+ boolean keep = false;
+ try {
+ keep = idler.queueIdle();
+ } catch (Throwable t) {
+ Log.wtf(TAG, "IdleHandler threw exception", t);
+ }
+
+ if (!keep) {
+ synchronized (mIdleHandlersLock) {
+ mIdleHandlers.remove(idler);
+ }
+ }
+ }
+
+ // Reset the idle handler count to 0 so we do not run them again.
+ pendingIdleHandlerCount = 0;
+
+ // While calling an idle handler, a new message could have been delivered
+ // so go back and look again for a pending message without waiting.
+ mNextPollTimeoutMillis = 0;
+ }
+ }
+
+ void quit(boolean safe) {
+ if (!mQuitAllowed) {
+ throw new IllegalStateException("Main thread not allowed to quit.");
+ }
+ synchronized (mIdleHandlersLock) {
+ if (sQuitting.compareAndSet(this, false, true)) {
+ if (safe) {
+ removeAllFutureMessages();
+ } else {
+ removeAllMessages();
+ }
+
+ // We can assume mPtr != 0 because sQuitting was previously false.
+ nativeWake(mPtr);
+ }
+ }
+ }
+
+ boolean enqueueMessage(@NonNull Message msg, long when) {
+ if (msg.target == null) {
+ throw new IllegalArgumentException("Message must have a target.");
+ }
+
+ if (msg.isInUse()) {
+ throw new IllegalStateException(msg + " This message is already in use.");
+ }
+
+ return enqueueMessageUnchecked(msg, when);
+ }
+
+ private boolean enqueueMessageUnchecked(@NonNull Message msg, long when) {
+ if ((boolean) sQuitting.getVolatile(this)) {
+ IllegalStateException e = new IllegalStateException(
+ msg.target + " sending message to a Handler on a dead thread");
+ Log.w(TAG, e.getMessage(), e);
+ msg.recycleUnchecked();
+ return false;
+ }
+
+ long seq = when != 0 ? ((long)sNextInsertSeq.getAndAdd(this, 1L) + 1L)
+ : ((long)sNextFrontInsertSeq.getAndAdd(this, -1L) - 1L);
+ /* TODO: Add a MessageNode member to Message so we can avoid this allocation */
+ MessageNode node = new MessageNode(msg, seq);
+ msg.when = when;
+ msg.markInUse();
+
+ if (DEBUG) {
+ Log.d(TAG, "Insert message what: " + msg.what + " when: " + msg.when + " seq: "
+ + node.mInsertSeq + " barrier: " + node.isBarrier() + " async: "
+ + node.isAsync() + " now: " + SystemClock.uptimeMillis());
+ }
+
+ while (true) {
+ StackNode old = (StackNode) sState.getVolatile(this);
+ boolean wakeNeeded;
+ boolean inactive;
+
+ node.mNext = old;
+ switch (old.getNodeType()) {
+ case STACK_NODE_ACTIVE:
+ /*
+ * The worker thread is currently active and will process any elements added to
+ * the stack before parking again.
+ */
+ node.mBottomOfStack = (StateNode) old;
+ inactive = false;
+ node.mWokeUp = true;
+ wakeNeeded = false;
+ break;
+
+ case STACK_NODE_PARKED:
+ node.mBottomOfStack = (StateNode) old;
+ inactive = true;
+ node.mWokeUp = true;
+ wakeNeeded = true;
+ break;
+
+ case STACK_NODE_TIMEDPARK:
+ node.mBottomOfStack = (StateNode) old;
+ inactive = true;
+ wakeNeeded = mStackStateTimedPark.mWhenToWake >= node.getWhen();
+ node.mWokeUp = wakeNeeded;
+ break;
+
+ default:
+ MessageNode oldMessage = (MessageNode) old;
+
+ node.mBottomOfStack = oldMessage.mBottomOfStack;
+ int bottomType = node.mBottomOfStack.getNodeType();
+ inactive = bottomType >= STACK_NODE_PARKED;
+ wakeNeeded = (bottomType == STACK_NODE_TIMEDPARK
+ && mStackStateTimedPark.mWhenToWake >= node.getWhen()
+ && !oldMessage.mWokeUp);
+ node.mWokeUp = oldMessage.mWokeUp || wakeNeeded;
+ break;
+ }
+ if (sState.compareAndSet(this, old, node)) {
+ if (inactive) {
+ if (wakeNeeded) {
+ nativeWake(mPtr);
+ } else {
+ mMessageCounts.incrementQueued();
+ }
+ }
+ return true;
+ }
+ }
+ }
+
+ /**
+ * Posts a synchronization barrier to the Looper's message queue.
+ *
+ * Message processing occurs as usual until the message queue encounters the
+ * synchronization barrier that has been posted. When the barrier is encountered,
+ * later synchronous messages in the queue are stalled (prevented from being executed)
+ * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
+ * the token that identifies the synchronization barrier.
+ *
+ * This method is used to immediately postpone execution of all subsequently posted
+ * synchronous messages until a condition is met that releases the barrier.
+ * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
+ * and continue to be processed as usual.
+ *
+ * This call must be always matched by a call to {@link #removeSyncBarrier} with
+ * the same token to ensure that the message queue resumes normal operation.
+ * Otherwise the application will probably hang!
+ *
+ * @return A token that uniquely identifies the barrier. This token must be
+ * passed to {@link #removeSyncBarrier} to release the barrier.
+ *
+ * @hide
+ */
+ @TestApi
+ public int postSyncBarrier() {
+ return postSyncBarrier(SystemClock.uptimeMillis());
+ }
+
+ private int postSyncBarrier(long when) {
+ final int token = mNextBarrierToken.getAndIncrement();
+ final Message msg = Message.obtain();
+
+ msg.markInUse();
+ msg.arg1 = token;
+
+ if (!enqueueMessageUnchecked(msg, when)) {
+ Log.wtf(TAG, "Unexpected error while adding sync barrier!");
+ return -1;
+ }
+
+ return token;
+ }
+
+ private class MatchBarrierToken extends MessageCompare {
+ int mBarrierToken;
+
+ MatchBarrierToken(int token) {
+ super();
+ mBarrierToken = token;
+ }
+
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == null && m.arg1 == mBarrierToken) {
+ return true;
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Removes a synchronization barrier.
+ *
+ * @param token The synchronization barrier token that was returned by
+ * {@link #postSyncBarrier}.
+ *
+ * @throws IllegalStateException if the barrier was not found.
+ *
+ * @hide
+ */
+ @TestApi
+ public void removeSyncBarrier(int token) {
+ boolean removed;
+ MessageNode first;
+ final MatchBarrierToken matchBarrierToken = new MatchBarrierToken(token);
+
+ synchronized (mPriorityQueue) {
+ try {
+ /* Retain the first element to see if we are currently stuck on a barrier. */
+ first = mPriorityQueue.peek();
+ } catch (NoSuchElementException e) {
+ /* The queue is empty */
+ first = null;
+ }
+
+ removed = findOrRemoveMessages(null, 0, null, null, 0, matchBarrierToken, true);
+ if (removed && first != null) {
+ Message m = first.mMessage;
+ if (m.target == null && m.arg1 == token) {
+ /* Wake up next() in case it was sleeping on this barrier. */
+ nativeWake(mPtr);
+ }
+ } else if (!removed) {
+ throw new IllegalStateException("The specified message queue synchronization "
+ + " barrier token has not been posted or has already been removed.");
+ }
+ }
+ }
+
+ private StateNode getStateNode(StackNode node) {
+ if (node.isMessageNode()) {
+ return ((MessageNode) node).mBottomOfStack;
+ }
+ return (StateNode) node;
+ }
+
+ /*
+ * This class is used to find matches for hasMessages() and removeMessages()
+ */
+ private abstract static class MessageCompare {
+ public abstract boolean compareMessage(Message m, Handler h, int what, Object object,
+ Runnable r, long when);
+ }
+ @GuardedBy("mPriorityQueue")
+ private boolean stackHasMessages(Handler h, int what, Object object, Runnable r, long when,
+ MessageCompare compare, boolean removeMatches) {
+ boolean found = false;
+ StackNode top = (StackNode) sState.getVolatile(this);
+ StateNode bottom = getStateNode(top);
+
+ /* No messages to search. */
+ if (!top.isMessageNode()) {
+ return false;
+ }
+
+ /*
+ * We have messages that we may tombstone. Walk the stack until we hit the bottom.
+ * next() will remove them on it's next pass.
+ */
+ if (!(top instanceof MessageNode)) {
+ Log.wtf(TAG, "Unknown node type found in Trieber stack");
+ }
+ MessageNode p = (MessageNode) top;
+
+ while (true) {
+ if (compare.compareMessage(p.mMessage, h, what, object, r, when)) {
+ found = true;
+ if (DEBUG) {
+ Log.w(TAG, "stackHasMessages node matches");
+ }
+ if (removeMatches) {
+ if (p.removeFromStack()) {
+ p.mMessage.recycleUnchecked();
+ if (mMessageCounts.incrementCancelled()) {
+ nativeWake(mPtr);
+ }
+ }
+ } else {
+ return true;
+ }
+ }
+
+ StackNode n = p.mNext;
+ if (!n.isMessageNode()) {
+ /* We reached the end of the stack */
+ return found;
+ }
+ p = (MessageNode) n;
+ }
+ }
+
+ @GuardedBy("mPriorityQueue")
+ private boolean priorityQueueHasMessage(PriorityQueue queue, Handler h,
+ int what, Object object, Runnable r, long when, MessageCompare compare,
+ boolean removeMatches) {
+ Iterator<MessageNode> iterator = queue.iterator();
+ boolean found = false;
+
+ while (iterator.hasNext()) {
+ MessageNode msg = iterator.next();
+
+ if (compare.compareMessage(msg.mMessage, h, what, object, r, when)) {
+ if (removeMatches) {
+ found = true;
+ iterator.remove();
+ msg.mMessage.recycleUnchecked();
+ } else {
+ return true;
+ }
+ }
+ }
+ return found;
+ }
+
+ private boolean findOrRemoveMessages(Handler h, int what, Object object, Runnable r, long when,
+ MessageCompare compare, boolean removeMatches) {
+ boolean foundInStack, foundInQueue;
+
+ synchronized (mPriorityQueue) {
+ foundInStack = stackHasMessages(h, what, object, r, when, compare, removeMatches);
+ foundInQueue = priorityQueueHasMessage(mPriorityQueue, h, what, object, r, when,
+ compare, removeMatches);
+ foundInQueue |= priorityQueueHasMessage(mAsyncPriorityQueue, h, what, object, r, when,
+ compare, removeMatches);
+
+ return foundInStack || foundInQueue;
+ }
+ }
+
+ private static class MatchHandlerWhatAndObject extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.what == what && (object == null || m.obj == object)) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerWhatAndObject mMatchHandlerWhatAndObject =
+ new MatchHandlerWhatAndObject();
+ boolean hasMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return false;
+ }
+
+ return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, false);
+ }
+
+ private static class MatchHandlerWhatAndObjectEquals extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.what == what && (object == null || object.equals(m.obj))) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerWhatAndObjectEquals mMatchHandlerWhatAndObjectEquals =
+ new MatchHandlerWhatAndObjectEquals();
+ boolean hasEqualMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return false;
+ }
+
+ return findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals,
+ false);
+ }
+
+ private static class MatchHandlerRunnableAndObject extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.callback == r && (object == null || m.obj == object)) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerRunnableAndObject mMatchHandlerRunnableAndObject =
+ new MatchHandlerRunnableAndObject();
+
+ boolean hasMessages(Handler h, Runnable r, Object object) {
+ if (h == null) {
+ return false;
+ }
+
+ return findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, false);
+ }
+
+ private static class MatchHandler extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandler mMatchHandler = new MatchHandler();
+ boolean hasMessages(Handler h) {
+ if (h == null) {
+ return false;
+ }
+ return findOrRemoveMessages(h, -1, null, null, 0, mMatchHandler, false);
+ }
+
+ void removeMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return;
+ }
+ findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObject, true);
+ }
+
+ void removeEqualMessages(Handler h, int what, Object object) {
+ if (h == null) {
+ return;
+ }
+ findOrRemoveMessages(h, what, object, null, 0, mMatchHandlerWhatAndObjectEquals, true);
+ }
+
+ void removeMessages(Handler h, Runnable r, Object object) {
+ if (h == null || r == null) {
+ return;
+ }
+ findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObject, true);
+ }
+
+ private static class MatchHandlerRunnableAndObjectEquals extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && m.callback == r && (object == null || object.equals(m.obj))) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerRunnableAndObjectEquals mMatchHandlerRunnableAndObjectEquals =
+ new MatchHandlerRunnableAndObjectEquals();
+ void removeEqualMessages(Handler h, Runnable r, Object object) {
+ if (h == null || r == null) {
+ return;
+ }
+ findOrRemoveMessages(h, -1, object, r, 0, mMatchHandlerRunnableAndObjectEquals, true);
+ }
+
+ private static class MatchHandlerAndObject extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && (object == null || m.obj == object)) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerAndObject mMatchHandlerAndObject = new MatchHandlerAndObject();
+ void removeCallbacksAndMessages(Handler h, Object object) {
+ if (h == null) {
+ return;
+ }
+ findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObject, true);
+ }
+
+ private static class MatchHandlerAndObjectEquals extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.target == h && (object == null || object.equals(m.obj))) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchHandlerAndObjectEquals mMatchHandlerAndObjectEquals =
+ new MatchHandlerAndObjectEquals();
+ void removeCallbacksAndEqualMessages(Handler h, Object object) {
+ if (h == null) {
+ return;
+ }
+ findOrRemoveMessages(h, -1, object, null, 0, mMatchHandlerAndObjectEquals, true);
+ }
+
+ private static class MatchAllMessages extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ return true;
+ }
+ }
+ private final MatchAllMessages mMatchAllMessages = new MatchAllMessages();
+ private void removeAllMessages() {
+ findOrRemoveMessages(null, -1, null, null, 0, mMatchAllMessages, true);
+ }
+
+ private static class MatchAllFutureMessages extends MessageCompare {
+ @Override
+ public boolean compareMessage(Message m, Handler h, int what, Object object, Runnable r,
+ long when) {
+ if (m.when > when) {
+ return true;
+ }
+ return false;
+ }
+ }
+ private final MatchAllFutureMessages mMatchAllFutureMessages = new MatchAllFutureMessages();
+ private void removeAllFutureMessages() {
+ findOrRemoveMessages(null, -1, null, null, SystemClock.uptimeMillis(),
+ mMatchAllFutureMessages, true);
+ }
+
+ private void printPriorityQueueNodes() {
+ Iterator<MessageNode> iterator = mPriorityQueue.iterator();
+
+ Log.d(TAG, "* Dump priority queue");
+ while (iterator.hasNext()) {
+ MessageNode msgNode = iterator.next();
+ Log.d(TAG, "** MessageNode what: " + msgNode.mMessage.what + " when "
+ + msgNode.mMessage.when + " seq: " + msgNode.mInsertSeq);
+ }
+ }
+
+ private int dumpPriorityQueue(PriorityQueue<MessageNode> queue, Printer pw, String prefix,
+ Handler h, int n) {
+ int count = 0;
+ long now = SystemClock.uptimeMillis();
+
+ for (MessageNode msgNode : queue) {
+ Message msg = msgNode.mMessage;
+ if (h == null || h == msg.target) {
+ pw.println(prefix + "Message " + (n + count) + ": " + msg.toString(now));
+ }
+ count++;
+ }
+ return count;
+ }
+
+ void dump(Printer pw, String prefix, Handler h) {
+ long now = SystemClock.uptimeMillis();
+ int n = 0;
+
+ pw.println(prefix + "(MessageQueue is using SemiConcurrent implementation)");
+
+ StackNode node = (StackNode) sState.getVolatile(this);
+ while (node != null) {
+ if (node.isMessageNode()) {
+ Message msg = ((MessageNode) node).mMessage;
+ if (h == null || h == msg.target) {
+ pw.println(prefix + "Message " + n + ": " + msg.toString(now));
+ }
+ node = ((MessageNode) node).mNext;
+ } else {
+ pw.println(prefix + "State: " + node);
+ node = null;
+ }
+ n++;
+ }
+
+ synchronized (mPriorityQueue) {
+ pw.println(prefix + "PriorityQueue Messages: ");
+ n += dumpPriorityQueue(mPriorityQueue, pw, prefix, h, n);
+ pw.println(prefix + "AsyncPriorityQueue Messages: ");
+ n += dumpPriorityQueue(mAsyncPriorityQueue, pw, prefix, h, n);
+ }
+
+ pw.println(prefix + "(Total messages: " + n + ", polling=" + isPolling()
+ + ", quitting=" + (boolean) sQuitting.getVolatile(this) + ")");
+ }
+
+ private int dumpPriorityQueue(PriorityQueue<MessageNode> queue, ProtoOutputStream proto) {
+ int count = 0;
+
+ for (MessageNode msgNode : queue) {
+ Message msg = msgNode.mMessage;
+ msg.dumpDebug(proto, MessageQueueProto.MESSAGES);
+ count++;
+ }
+ return count;
+ }
+
+ void dumpDebug(ProtoOutputStream proto, long fieldId) {
+ final long messageQueueToken = proto.start(fieldId);
+
+ StackNode node = (StackNode) sState.getVolatile(this);
+ while (node.isMessageNode()) {
+ Message msg = ((MessageNode) node).mMessage;
+ msg.dumpDebug(proto, MessageQueueProto.MESSAGES);
+ node = ((MessageNode) node).mNext;
+ }
+
+ synchronized (mPriorityQueue) {
+ dumpPriorityQueue(mPriorityQueue, proto);
+ dumpPriorityQueue(mAsyncPriorityQueue, proto);
+ }
+
+ proto.write(MessageQueueProto.IS_POLLING_LOCKED, isPolling());
+ proto.write(MessageQueueProto.IS_QUITTING, (boolean) sQuitting.getVolatile(this));
+ proto.end(messageQueueToken);
+ }
+
+ /**
+ * Adds a file descriptor listener to receive notification when file descriptor
+ * related events occur.
+ * <p>
+ * If the file descriptor has already been registered, the specified events
+ * and listener will replace any that were previously associated with it.
+ * It is not possible to set more than one listener per file descriptor.
+ * </p><p>
+ * It is important to always unregister the listener when the file descriptor
+ * is no longer of use.
+ * </p>
+ *
+ * @param fd The file descriptor for which a listener will be registered.
+ * @param events The set of events to receive: a combination of the
+ * {@link OnFileDescriptorEventListener#EVENT_INPUT},
+ * {@link OnFileDescriptorEventListener#EVENT_OUTPUT}, and
+ * {@link OnFileDescriptorEventListener#EVENT_ERROR} event masks. If the requested
+ * set of events is zero, then the listener is unregistered.
+ * @param listener The listener to invoke when file descriptor events occur.
+ *
+ * @see OnFileDescriptorEventListener
+ * @see #removeOnFileDescriptorEventListener
+ */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,
+ @OnFileDescriptorEventListener.Events int events,
+ @NonNull OnFileDescriptorEventListener listener) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+
+ synchronized (mFileDescriptorRecordsLock) {
+ updateOnFileDescriptorEventListenerLocked(fd, events, listener);
+ }
+ }
+
+ /**
+ * Removes a file descriptor listener.
+ * <p>
+ * This method does nothing if no listener has been registered for the
+ * specified file descriptor.
+ * </p>
+ *
+ * @param fd The file descriptor whose listener will be unregistered.
+ *
+ * @see OnFileDescriptorEventListener
+ * @see #addOnFileDescriptorEventListener
+ */
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ public void removeOnFileDescriptorEventListener(@NonNull FileDescriptor fd) {
+ if (fd == null) {
+ throw new IllegalArgumentException("fd must not be null");
+ }
+
+ synchronized (mFileDescriptorRecordsLock) {
+ updateOnFileDescriptorEventListenerLocked(fd, 0, null);
+ }
+ }
+
+ @android.ravenwood.annotation.RavenwoodThrow(blockedBy = android.os.ParcelFileDescriptor.class)
+ private void updateOnFileDescriptorEventListenerLocked(FileDescriptor fd, int events,
+ OnFileDescriptorEventListener listener) {
+ final int fdNum = fd.getInt$();
+
+ int index = -1;
+ FileDescriptorRecord record = null;
+ if (mFileDescriptorRecords != null) {
+ index = mFileDescriptorRecords.indexOfKey(fdNum);
+ if (index >= 0) {
+ record = mFileDescriptorRecords.valueAt(index);
+ if (record != null && record.mEvents == events) {
+ return;
+ }
+ }
+ }
+
+ if (events != 0) {
+ events |= OnFileDescriptorEventListener.EVENT_ERROR;
+ if (record == null) {
+ if (mFileDescriptorRecords == null) {
+ mFileDescriptorRecords = new SparseArray<FileDescriptorRecord>();
+ }
+ record = new FileDescriptorRecord(fd, events, listener);
+ mFileDescriptorRecords.put(fdNum, record);
+ } else {
+ record.mListener = listener;
+ record.mEvents = events;
+ record.mSeq += 1;
+ }
+ nativeSetFileDescriptorEvents(mPtr, fdNum, events);
+ } else if (record != null) {
+ record.mEvents = 0;
+ mFileDescriptorRecords.removeAt(index);
+ nativeSetFileDescriptorEvents(mPtr, fdNum, 0);
+ }
+ }
+
+ // Called from native code.
+ private int dispatchEvents(int fd, int events) {
+ // Get the file descriptor record and any state that might change.
+ final FileDescriptorRecord record;
+ final int oldWatchedEvents;
+ final OnFileDescriptorEventListener listener;
+ final int seq;
+ synchronized (mFileDescriptorRecordsLock) {
+ record = mFileDescriptorRecords.get(fd);
+ if (record == null) {
+ return 0; // spurious, no listener registered
+ }
+
+ oldWatchedEvents = record.mEvents;
+ events &= oldWatchedEvents; // filter events based on current watched set
+ if (events == 0) {
+ return oldWatchedEvents; // spurious, watched events changed
+ }
+
+ listener = record.mListener;
+ seq = record.mSeq;
+ }
+
+ // Invoke the listener outside of the lock.
+ int newWatchedEvents = listener.onFileDescriptorEvents(
+ record.mDescriptor, events);
+ if (newWatchedEvents != 0) {
+ newWatchedEvents |= OnFileDescriptorEventListener.EVENT_ERROR;
+ }
+
+ // Update the file descriptor record if the listener changed the set of
+ // events to watch and the listener itself hasn't been updated since.
+ if (newWatchedEvents != oldWatchedEvents) {
+ synchronized (mFileDescriptorRecordsLock) {
+ int index = mFileDescriptorRecords.indexOfKey(fd);
+ if (index >= 0 && mFileDescriptorRecords.valueAt(index) == record
+ && record.mSeq == seq) {
+ record.mEvents = newWatchedEvents;
+ if (newWatchedEvents == 0) {
+ mFileDescriptorRecords.removeAt(index);
+ }
+ }
+ }
+ }
+
+ // Return the new set of events to watch for native code to take care of.
+ return newWatchedEvents;
+ }
+
+ /**
+ * Callback interface for discovering when a thread is going to block
+ * waiting for more messages.
+ */
+ public static interface IdleHandler {
+ /**
+ * Called when the message queue has run out of messages and will now
+ * wait for more. Return true to keep your idle handler active, false
+ * to have it removed. This may be called if there are still messages
+ * pending in the queue, but they are all scheduled to be dispatched
+ * after the current time.
+ */
+ boolean queueIdle();
+ }
+
+ /**
+ * A listener which is invoked when file descriptor related events occur.
+ */
+ public interface OnFileDescriptorEventListener {
+ /**
+ * File descriptor event: Indicates that the file descriptor is ready for input
+ * operations, such as reading.
+ * <p>
+ * The listener should read all available data from the file descriptor
+ * then return <code>true</code> to keep the listener active or <code>false</code>
+ * to remove the listener.
+ * </p><p>
+ * In the case of a socket, this event may be generated to indicate
+ * that there is at least one incoming connection that the listener
+ * should accept.
+ * </p><p>
+ * This event will only be generated if the {@link #EVENT_INPUT} event mask was
+ * specified when the listener was added.
+ * </p>
+ */
+ public static final int EVENT_INPUT = 1 << 0;
+
+ /**
+ * File descriptor event: Indicates that the file descriptor is ready for output
+ * operations, such as writing.
+ * <p>
+ * The listener should write as much data as it needs. If it could not
+ * write everything at once, then it should return <code>true</code> to
+ * keep the listener active. Otherwise, it should return <code>false</code>
+ * to remove the listener then re-register it later when it needs to write
+ * something else.
+ * </p><p>
+ * This event will only be generated if the {@link #EVENT_OUTPUT} event mask was
+ * specified when the listener was added.
+ * </p>
+ */
+ public static final int EVENT_OUTPUT = 1 << 1;
+
+ /**
+ * File descriptor event: Indicates that the file descriptor encountered a
+ * fatal error.
+ * <p>
+ * File descriptor errors can occur for various reasons. One common error
+ * is when the remote peer of a socket or pipe closes its end of the connection.
+ * </p><p>
+ * This event may be generated at any time regardless of whether the
+ * {@link #EVENT_ERROR} event mask was specified when the listener was added.
+ * </p>
+ */
+ public static final int EVENT_ERROR = 1 << 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "EVENT_" }, value = {
+ EVENT_INPUT,
+ EVENT_OUTPUT,
+ EVENT_ERROR
+ })
+ public @interface Events {}
+
+ /**
+ * Called when a file descriptor receives events.
+ *
+ * @param fd The file descriptor.
+ * @param events The set of events that occurred: a combination of the
+ * {@link #EVENT_INPUT}, {@link #EVENT_OUTPUT}, and {@link #EVENT_ERROR} event masks.
+ * @return The new set of events to watch, or 0 to unregister the listener.
+ *
+ * @see #EVENT_INPUT
+ * @see #EVENT_OUTPUT
+ * @see #EVENT_ERROR
+ */
+ @Events int onFileDescriptorEvents(@NonNull FileDescriptor fd, @Events int events);
+ }
+
+ static final class FileDescriptorRecord {
+ public final FileDescriptor mDescriptor;
+ public int mEvents;
+ public OnFileDescriptorEventListener mListener;
+ public int mSeq;
+
+ public FileDescriptorRecord(FileDescriptor descriptor,
+ int events, OnFileDescriptorEventListener listener) {
+ mDescriptor = descriptor;
+ mEvents = events;
+ mListener = listener;
+ }
+ }
+}
diff --git a/core/java/android/os/connectivity/CellularBatteryStats.java b/core/java/android/os/connectivity/CellularBatteryStats.java
index fc17002..1649ed5 100644
--- a/core/java/android/os/connectivity/CellularBatteryStats.java
+++ b/core/java/android/os/connectivity/CellularBatteryStats.java
@@ -15,11 +15,13 @@
*/
package android.os.connectivity;
+import android.annotation.FlaggedApi;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.BatteryStats;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,6 +37,7 @@
*
* @hide
*/
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
@SystemApi
public final class CellularBatteryStats implements Parcelable {
@@ -83,11 +86,17 @@
}
};
- /** @hide **/
+ /**
+ * This constructor should only be used in tests.
+ * @hide
+ */
+ @FlaggedApi(
+ com.android.server.power.optimization.Flags.FLAG_STREAMLINED_CONNECTIVITY_BATTERY_STATS)
+ @TestApi
public CellularBatteryStats(long loggingDurationMs, long kernelActiveTimeMs, long numPacketsTx,
long numBytesTx, long numPacketsRx, long numBytesRx, long sleepTimeMs, long idleTimeMs,
- long rxTimeMs, Long energyConsumedMaMs, long[] timeInRatMs,
- long[] timeInRxSignalStrengthLevelMs, long[] txTimeMs,
+ long rxTimeMs, long energyConsumedMaMs, @NonNull long[] timeInRatMs,
+ @NonNull long[] timeInRxSignalStrengthLevelMs, @NonNull long[] txTimeMs,
long monitoredRailChargeConsumedMaMs) {
mLoggingDurationMs = loggingDurationMs;
@@ -270,7 +279,6 @@
* @return The amount of time the phone spends in the {@code networkType} network type. The
* unit is in microseconds.
*/
- @NonNull
@SuppressLint("MethodNameUnits")
public long getTimeInRatMicros(@NetworkType int networkType) {
if (networkType >= mTimeInRatMs.length) {
@@ -289,7 +297,6 @@
* @return Amount of time phone spends in specific cellular rx signal strength levels
* in microseconds. The index is signal strength bin.
*/
- @NonNull
@SuppressLint("MethodNameUnits")
public long getTimeInRxSignalStrengthLevelMicros(
@IntRange(from = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
@@ -315,10 +322,9 @@
* <li> index 3 = 15dBm < tx_power < 20dBm. </li>
* <li> index 4 = tx_power > 20dBm. </li>
* </ul>
- *
- * @hide
*/
- @NonNull
+ @FlaggedApi(
+ com.android.server.power.optimization.Flags.FLAG_STREAMLINED_CONNECTIVITY_BATTERY_STATS)
public long getTxTimeMillis(
@IntRange(from = ModemActivityInfo.TX_POWER_LEVEL_0,
to = ModemActivityInfo.TX_POWER_LEVEL_4) int level) {
diff --git a/core/java/android/os/connectivity/WifiBatteryStats.java b/core/java/android/os/connectivity/WifiBatteryStats.java
index 7e6ebcf..79e0be8 100644
--- a/core/java/android/os/connectivity/WifiBatteryStats.java
+++ b/core/java/android/os/connectivity/WifiBatteryStats.java
@@ -19,9 +19,11 @@
import static android.os.BatteryStatsManager.NUM_WIFI_STATES;
import static android.os.BatteryStatsManager.NUM_WIFI_SUPPL_STATES;
+import android.annotation.FlaggedApi;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -34,6 +36,7 @@
* @hide
*/
@SystemApi
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
public final class WifiBatteryStats implements Parcelable {
private final long mLoggingDurationMillis;
private final long mKernelActiveTimeMillis;
@@ -150,7 +153,13 @@
mMonitoredRailChargeConsumedMaMillis);
}
- /** @hide **/
+ /**
+ * This constructor should only be used in tests.
+ * @hide
+ */
+ @FlaggedApi(
+ com.android.server.power.optimization.Flags.FLAG_STREAMLINED_CONNECTIVITY_BATTERY_STATS)
+ @TestApi
public WifiBatteryStats(long loggingDurationMillis, long kernelActiveTimeMillis,
long numPacketsTx, long numBytesTx, long numPacketsRx, long numBytesRx,
long sleepTimeMillis, long scanTimeMillis, long idleTimeMillis, long rxTimeMillis,
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 493d676..57853e7 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1403,6 +1403,19 @@
"android.settings.QUICK_LAUNCH_SETTINGS";
/**
+ * Activity Action: Showing settings to manage adaptive notifications.
+ * <p>
+ * Input: Nothing.
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_ADAPTIVE_NOTIFICATIONS =
+ "android.settings.MANAGE_ADAPTIVE_NOTIFICATIONS";
+
+ /**
* Activity Action: Show settings to manage installed applications.
* <p>
* In some cases, a matching Activity may not exist, so ensure you
@@ -20094,7 +20107,7 @@
* (0 = false, 1 = true)
* @hide
*/
- @Readable(maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ @Readable
public static final String REDUCE_MOTION = "reduce_motion";
/**
diff --git a/core/java/android/view/IPinnedTaskListener.aidl b/core/java/android/view/IPinnedTaskListener.aidl
index e4e2d6f..3bd1506 100644
--- a/core/java/android/view/IPinnedTaskListener.aidl
+++ b/core/java/android/view/IPinnedTaskListener.aidl
@@ -42,12 +42,4 @@
* with fromImeAdjustement set to {@code true}.
*/
void onImeVisibilityChanged(boolean imeVisible, int imeHeight);
-
- /**
- * Called by the window manager to notify the listener that Activity (was or is in pinned mode)
- * is hidden (either stopped or removed). This is generally used as a signal to reset saved
- * reentry fraction and size.
- * {@param componentName} represents the application component of PiP window.
- */
- void onActivityHidden(in ComponentName componentName);
}
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index f653524..634469d 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -72,7 +72,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
-import com.android.internal.util.VirtualRefBasePtr;
import com.android.window.flags.Flags;
import dalvik.system.CloseGuard;
@@ -273,10 +272,12 @@
String windowName, int displayId);
private static native void nativeSetFrameTimelineVsync(long transactionObj,
long frameTimelineVsyncId);
- private static native void nativeAddJankDataListener(long nativeListener,
- long nativeSurfaceControl);
- private static native void nativeRemoveJankDataListener(long nativeListener);
- private static native long nativeCreateJankDataListenerWrapper(OnJankDataListener listener);
+ private static native long nativeCreateJankDataListenerWrapper(
+ long surfaceControl, OnJankDataListener listener);
+ private static native long nativeGetJankDataListenerWrapperFinalizer();
+ private static native void nativeAddJankDataListener(long nativeListener);
+ private static native void nativeFlushJankData(long nativeListener);
+ private static native void nativeRemoveJankDataListener(long nativeListener, long afterVsync);
private static native int nativeGetGPUContextPriority();
private static native void nativeSetTransformHint(long nativeObject,
@SurfaceControl.BufferTransform int transformHint);
@@ -461,17 +462,63 @@
* @see #addJankDataListener
* @hide
*/
- public static abstract class OnJankDataListener {
- private final VirtualRefBasePtr mNativePtr;
-
- public OnJankDataListener() {
- mNativePtr = new VirtualRefBasePtr(nativeCreateJankDataListenerWrapper(this));
- }
-
+ public interface OnJankDataListener {
/**
* Called when new jank classifications are available.
*/
- public abstract void onJankDataAvailable(JankData[] jankStats);
+ void onJankDataAvailable(JankData[] jankData);
+
+ }
+
+ /**
+ * Handle to a registered {@link OnJankDatalistener}.
+ * @hide
+ */
+ public static class OnJankDataListenerRegistration {
+ private final long mNativeObject;
+
+ private static final NativeAllocationRegistry sRegistry =
+ NativeAllocationRegistry.createMalloced(
+ OnJankDataListenerRegistration.class.getClassLoader(),
+ nativeGetJankDataListenerWrapperFinalizer());
+
+ private final Runnable mFreeNativeResources;
+ private boolean mRemoved = false;
+
+ OnJankDataListenerRegistration(SurfaceControl surface, OnJankDataListener listener) {
+ mNativeObject = nativeCreateJankDataListenerWrapper(surface.mNativeObject, listener);
+ mFreeNativeResources = (mNativeObject == 0) ? () -> {} :
+ sRegistry.registerNativeAllocation(this, mNativeObject);
+ }
+
+ /**
+ * Request a flush of any pending jank classification data. May cause the registered
+ * listener to be invoked inband.
+ */
+ public void flush() {
+ nativeFlushJankData(mNativeObject);
+ }
+
+ /**
+ * Request the removal of the registered listener after the VSync with the provided ID. Use
+ * a value <= 0 for afterVsync to remove the listener immediately. The given listener will
+ * not be removed before the given VSync, but may still reveive data for frames past the
+ * provided VSync.
+ */
+ public void removeAfter(long afterVsync) {
+ mRemoved = true;
+ nativeRemoveJankDataListener(mNativeObject, afterVsync);
+ }
+
+ /**
+ * Free the native resources associated with the listener registration.
+ */
+ public void release() {
+ if (!mRemoved) {
+ removeAfter(0);
+ }
+ mFreeNativeResources.run();
+ }
}
private final CloseGuard mCloseGuard = CloseGuard.get();
@@ -2632,19 +2679,11 @@
}
/**
- * Adds a callback to be informed about SF's jank classification for a specific surface.
+ * Adds a callback to be informed about SF's jank classification for this surface.
* @hide
*/
- public static void addJankDataListener(OnJankDataListener listener, SurfaceControl surface) {
- nativeAddJankDataListener(listener.mNativePtr.get(), surface.mNativeObject);
- }
-
- /**
- * Removes a jank callback previously added with {@link #addJankDataListener}
- * @hide
- */
- public static void removeJankDataListener(OnJankDataListener listener) {
- nativeRemoveJankDataListener(listener.mNativePtr.get());
+ public OnJankDataListenerRegistration addJankDataListener(OnJankDataListener listener) {
+ return new OnJankDataListenerRegistration(this, listener);
}
/**
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 4766942..766e02b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -13859,6 +13859,11 @@
})
@ResolvedLayoutDir
public int getLayoutDirection() {
+ final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
+ if (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+ return LAYOUT_DIRECTION_RESOLVED_DEFAULT;
+ }
return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ==
PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
}
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/android/view/autofill/OWNERS b/core/java/android/view/autofill/OWNERS
index 898947a..7f3b4e5 100644
--- a/core/java/android/view/autofill/OWNERS
+++ b/core/java/android/view/autofill/OWNERS
@@ -1,10 +1,11 @@
# Bug component: 351486
-simranjit@google.com
haoranzhang@google.com
+jiewenlei@google.com
+simranjit@google.com
skxu@google.com
+shuc@google.com
yunicorn@google.com
-reemabajwa@google.com
# Bug component: 543785 = per-file *Augmented*
per-file *Augmented* = wangqi@google.com
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 21d6184..9512347 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -62,6 +62,7 @@
import android.content.res.loader.ResourcesLoader;
import android.content.res.loader.ResourcesProvider;
import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.BlendMode;
import android.graphics.Outline;
import android.graphics.PorterDuff;
@@ -90,6 +91,7 @@
import android.util.IntArray;
import android.util.Log;
import android.util.LongArray;
+import android.util.LongSparseArray;
import android.util.Pair;
import android.util.SizeF;
import android.util.SparseArray;
@@ -98,6 +100,7 @@
import android.util.TypedValue.ComplexDimensionUnit;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoStream;
import android.util.proto.ProtoUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -1266,11 +1269,16 @@
int intentId = in.readInt();
String intentUri = in.readString8();
RemoteCollectionItems items = new RemoteCollectionItems(in, currentRootData);
- mIdToUriMapping.put(intentId, intentUri);
- mUriToCollectionMapping.put(intentUri, items);
+ addMapping(intentId, intentUri, items);
}
}
+ void addMapping(int intentId, String intentUri, RemoteCollectionItems items) {
+ mIdToUriMapping.put(intentId, intentUri);
+ mUriToCollectionMapping.put(intentUri, items);
+ }
+
+
void setHierarchyDataForId(int intentId, HierarchyRootData data) {
String uri = mIdToUriMapping.get(intentId);
if (mUriToCollectionMapping.get(uri) == null) {
@@ -1465,6 +1473,87 @@
mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true);
}
}
+
+ public void writeToProto(Context context, ProtoOutputStream out) {
+ final long token = out.start(RemoteViewsProto.REMOTE_COLLECTION_CACHE);
+ for (int i = 0; i < mIdToUriMapping.size(); i++) {
+ final long entryToken = out.start(RemoteViewsProto.RemoteCollectionCache.ENTRIES);
+ out.write(RemoteViewsProto.RemoteCollectionCache.Entry.ID,
+ mIdToUriMapping.keyAt(i));
+ String intentUri = mIdToUriMapping.valueAt(i);
+ out.write(RemoteViewsProto.RemoteCollectionCache.Entry.URI, intentUri);
+ final long itemsToken = out.start(
+ RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS);
+ mUriToCollectionMapping.get(intentUri).writeToProto(context, out, /* attached= */
+ true);
+ out.end(itemsToken);
+ out.end(entryToken);
+ }
+ out.end(token);
+ }
+ }
+
+ private PendingResources<RemoteCollectionCache> populateRemoteCollectionCacheFromProto(
+ ProtoInputStream in) throws Exception {
+ final ArrayList<LongSparseArray<Object>> entries = new ArrayList<>();
+ final long token = in.start(RemoteViewsProto.REMOTE_COLLECTION_CACHE);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.RemoteCollectionCache.ENTRIES:
+ final LongSparseArray<Object> entry = new LongSparseArray<>();
+ final long entryToken = in.start(
+ RemoteViewsProto.RemoteCollectionCache.ENTRIES);
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.RemoteCollectionCache.Entry.ID:
+ entry.put(RemoteViewsProto.RemoteCollectionCache.Entry.ID,
+ in.readInt(
+ RemoteViewsProto.RemoteCollectionCache.Entry.ID));
+ break;
+ case (int) RemoteViewsProto.RemoteCollectionCache.Entry.URI:
+ entry.put(RemoteViewsProto.RemoteCollectionCache.Entry.URI,
+ in.readString(
+ RemoteViewsProto.RemoteCollectionCache.Entry.URI));
+ break;
+ case (int) RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS:
+ final long itemsToken = in.start(
+ RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS);
+ entry.put(RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS,
+ RemoteCollectionItems.createFromProto(in));
+ in.end(itemsToken);
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(entryToken);
+ checkContainsKeys(entry,
+ new long[]{RemoteViewsProto.RemoteCollectionCache.Entry.ID,
+ RemoteViewsProto.RemoteCollectionCache.Entry.URI,
+ RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS});
+ entries.add(entry);
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+ in.end(token);
+
+ return (context, resources, rootData, depth) -> {
+ for (LongSparseArray<Object> entry : entries) {
+ int id = (int) entry.get(RemoteViewsProto.RemoteCollectionCache.Entry.ID);
+ String uri = (String) entry.get(RemoteViewsProto.RemoteCollectionCache.Entry.URI);
+ // Depth resets to 0 for RemoteCollectionItems
+ RemoteCollectionItems items = ((PendingResources<RemoteCollectionItems>) entry.get(
+ RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS)).create(context,
+ resources, rootData, depth);
+ rootData.mRemoteCollectionCache.addMapping(id, uri, items);
+ }
+ // Redundant return, but type signature requires we return something.
+ return rootData.mRemoteCollectionCache;
+ };
}
private class SetRemoteViewsAdapterIntent extends Action {
@@ -2080,6 +2169,15 @@
dest.writeTypedList(mBitmaps, flags);
}
+ public void writeBitmapsToProto(ProtoOutputStream out) {
+ for (int i = 0; i < mBitmaps.size(); i++) {
+ final Bitmap bitmap = mBitmaps.get(i);
+ final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100, bytes);
+ out.write(RemoteViewsProto.BITMAP_CACHE, bytes.toByteArray());
+ }
+ }
+
public int getBitmapMemory() {
if (mBitmapMemory < 0) {
mBitmapMemory = 0;
@@ -7522,6 +7620,127 @@
dest.restoreAllowSquashing(prevAllowSquashing);
}
+ /** @hide */
+ public void writeToProto(Context context, ProtoOutputStream out) {
+ writeToProto(context, out, /* attached= */ false);
+ }
+
+ private void writeToProto(Context context, ProtoOutputStream out, boolean attached) {
+ for (long id : mIds) {
+ out.write(RemoteViewsProto.RemoteCollectionItems.IDS, id);
+ }
+
+ boolean restoreRoot = false;
+ out.write(RemoteViewsProto.RemoteCollectionItems.ATTACHED, attached);
+ if (!attached && mViews.length > 0 && !mViews[0].mIsRoot) {
+ restoreRoot = true;
+ mViews[0].mIsRoot = true;
+ }
+ for (RemoteViews view : mViews) {
+ final long viewsToken = out.start(RemoteViewsProto.RemoteCollectionItems.VIEWS);
+ view.writePreviewToProto(context, out);
+ out.end(viewsToken);
+ }
+ if (restoreRoot) mViews[0].mIsRoot = false;
+ out.write(RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS, mHasStableIds);
+ out.write(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT, mViewTypeCount);
+ }
+
+ /**
+ * Overload used for testing unattached RemoteCollectionItems serialization.
+ *
+ * @hide
+ */
+ public static RemoteCollectionItems createFromProto(Context context, ProtoInputStream in)
+ throws Exception {
+ return createFromProto(in).create(context, context.getResources(), /* rootData= */
+ null, 0);
+ }
+
+ /** @hide */
+ public static PendingResources<RemoteCollectionItems> createFromProto(ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+ values.put(RemoteViewsProto.RemoteCollectionItems.IDS, new ArrayList<Long>());
+ values.put(RemoteViewsProto.RemoteCollectionItems.VIEWS,
+ new ArrayList<PendingResources<RemoteViews>>());
+ while (in.nextField() != NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.RemoteCollectionItems.IDS:
+ ((ArrayList<Long>) values.get(
+ RemoteViewsProto.RemoteCollectionItems.IDS)).add(
+ in.readLong(RemoteViewsProto.RemoteCollectionItems.IDS));
+ break;
+ case (int) RemoteViewsProto.RemoteCollectionItems.VIEWS:
+ final long viewsToken = in.start(
+ RemoteViewsProto.RemoteCollectionItems.VIEWS);
+ ((ArrayList<PendingResources<RemoteViews>>) values.get(
+ RemoteViewsProto.RemoteCollectionItems.VIEWS)).add(
+ RemoteViews.createFromProto(in));
+ in.end(viewsToken);
+ break;
+ case (int) RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS:
+ values.put(RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS,
+ in.readBoolean(
+ RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS));
+ break;
+ case (int) RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT:
+ values.put(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT,
+ in.readInt(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT));
+ break;
+ case (int) RemoteViewsProto.RemoteCollectionItems.ATTACHED:
+ values.put(RemoteViewsProto.RemoteCollectionItems.ATTACHED,
+ in.readBoolean(RemoteViewsProto.RemoteCollectionItems.ATTACHED));
+ break;
+ default:
+ Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+
+ checkContainsKeys(values,
+ new long[]{RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT});
+ return (context, resources, rootData, depth) -> {
+ List<Long> idList = (List<Long>) values.get(
+ RemoteViewsProto.RemoteCollectionItems.IDS);
+ long[] ids = new long[idList.size()];
+ for (int i = 0; i < idList.size(); i++) {
+ ids[i] = idList.get(i);
+ }
+ boolean attached = (boolean) values.get(
+ RemoteViewsProto.RemoteCollectionItems.ATTACHED, false);
+ List<PendingResources<RemoteViews>> pendingViews =
+ (List<PendingResources<RemoteViews>>) values.get(
+ RemoteViewsProto.RemoteCollectionItems.VIEWS);
+ RemoteViews[] views = new RemoteViews[pendingViews.size()];
+
+ if (attached && rootData == null) {
+ throw new IllegalStateException("Cannot create a RemoteCollectionItems from "
+ + "proto that was attached without providing HierarchyRootData");
+ }
+
+ int firstChildIndex = 0;
+ if (!attached && pendingViews.size() > 0) {
+ // If written as unattached, get HierarchyRootData from first view
+ views[0] = pendingViews.get(0).create(context, resources, /* rootData= */ null,
+ /* depth= */ 0);
+ rootData = views[0].getHierarchyRootData();
+ firstChildIndex = 1;
+ }
+ for (int i = firstChildIndex; i < views.length; i++) {
+ // Depth is reset to 0 for RemoteCollectionItems item views, see Parcel
+ // constructor.
+ views[i] = pendingViews.get(i).create(context, resources, rootData,
+ /* depth= */ 0);
+ }
+ return new RemoteCollectionItems(ids, views,
+ (boolean) values.get(RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS,
+ false),
+ (int) values.get(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT,
+ 0));
+ };
+ }
+
/**
* Returns the id for {@code position}. See {@link #hasStableIds()} for whether this id
* should be considered meaningful across collection updates.
@@ -7907,6 +8126,10 @@
if (mViewId != 0 && mViewId != -1) {
out.write(RemoteViewsProto.VIEW_ID, appResources.getResourceName(mViewId));
}
+ if (mIsRoot) {
+ mBitmapCache.writeBitmapsToProto(out);
+ mCollectionCache.writeToProto(context, out);
+ }
out.write(RemoteViewsProto.IS_ROOT, mIsRoot);
out.write(RemoteViewsProto.APPLY_FLAGS, mApplyFlags);
out.write(RemoteViewsProto.HAS_DRAW_INSTRUCTIONS, mHasDrawInstructions);
@@ -7968,6 +8191,7 @@
final List<PendingResources<RemoteViews>> mSizedRemoteViews = new ArrayList<>();
PendingResources<RemoteViews> mLandscapeViews = null;
PendingResources<RemoteViews> mPortraitViews = null;
+ PendingResources<RemoteCollectionCache> mPopulateRemoteCollectionCache = null;
boolean mIsRoot = false;
boolean mHasDrawInstructions = false;
};
@@ -8018,6 +8242,18 @@
ref.mPortraitViews = createFromProto(in);
in.end(portraitToken);
break;
+ case (int) RemoteViewsProto.BITMAP_CACHE:
+ byte[] src = in.readBytes(RemoteViewsProto.BITMAP_CACHE);
+ Bitmap bitmap = BitmapFactory.decodeByteArray(src, 0, src.length);
+ ref.mRv.mBitmapCache.getBitmapId(bitmap);
+ break;
+ case (int) RemoteViewsProto.REMOTE_COLLECTION_CACHE:
+ final long collectionToken = in.start(
+ RemoteViewsProto.REMOTE_COLLECTION_CACHE);
+ ref.mPopulateRemoteCollectionCache =
+ ref.mRv.populateRemoteCollectionCacheFromProto(in);
+ in.end(collectionToken);
+ break;
case (int) RemoteViewsProto.IS_ROOT:
ref.mIsRoot = in.readBoolean(RemoteViewsProto.IS_ROOT);
break;
@@ -8087,6 +8323,9 @@
rv.setLightBackgroundLayoutId(lightBackgroundLayoutId);
}
}
+ if (ref.mPopulateRemoteCollectionCache != null) {
+ ref.mPopulateRemoteCollectionCache.create(context, resources, rootData, depth);
+ }
if (ref.mProviderInstanceId != -1) {
rv.mProviderInstanceId = ref.mProviderInstanceId;
}
@@ -8139,6 +8378,16 @@
}
}
+ private static void checkContainsKeys(LongSparseArray<?> array, long[] requiredFields) {
+ for (long requiredField : requiredFields) {
+ if (array.indexOfKey(requiredField) < 0) {
+ throw new IllegalArgumentException(
+ "RemoteViews proto missing field: " + ProtoStream.getFieldIdString(
+ requiredField));
+ }
+ }
+ }
+
private static SizeF createSizeFFromProto(ProtoInputStream in) throws Exception {
float width = 0;
float height = 0;
diff --git a/core/java/android/window/flags/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig
index 69cac6f..94f6503 100644
--- a/core/java/android/window/flags/responsible_apis.aconfig
+++ b/core/java/android/window/flags/responsible_apis.aconfig
@@ -56,3 +56,11 @@
description: "Improved metrics."
bug: "339245692"
}
+
+flag {
+ name: "bal_send_intent_with_options"
+ namespace: "responsible_apis"
+ description: "Add options parameter to IntentSender.sendIntent."
+ bug: "339720406"
+}
+
diff --git a/core/java/com/android/internal/display/BrightnessSynchronizer.java b/core/java/com/android/internal/display/BrightnessSynchronizer.java
index 0068490..9f5ed65 100644
--- a/core/java/com/android/internal/display/BrightnessSynchronizer.java
+++ b/core/java/com/android/internal/display/BrightnessSynchronizer.java
@@ -78,9 +78,9 @@
// Feature flag that will eventually be removed
private final boolean mIntRangeUserPerceptionEnabled;
- public BrightnessSynchronizer(Context context, boolean intRangeUserPerceptionEnabled) {
- this(context, Looper.getMainLooper(), SystemClock::uptimeMillis,
- intRangeUserPerceptionEnabled);
+ public BrightnessSynchronizer(Context context, Looper looper,
+ boolean intRangeUserPerceptionEnabled) {
+ this(context, looper, SystemClock::uptimeMillis, intRangeUserPerceptionEnabled);
}
@VisibleForTesting
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/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 86729f7..bf5df03 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -63,8 +63,8 @@
* A class that allows the app to get the frame metrics from HardwareRendererObserver.
* @hide
*/
-public class FrameTracker extends SurfaceControl.OnJankDataListener
- implements HardwareRendererObserver.OnFrameMetricsAvailableListener {
+public class FrameTracker implements HardwareRendererObserver.OnFrameMetricsAvailableListener,
+ SurfaceControl.OnJankDataListener {
private static final String TAG = "FrameTracker";
private static final long INVALID_ID = -1;
@@ -118,6 +118,7 @@
public final boolean mSurfaceOnly;
private SurfaceControl mSurfaceControl;
+ private SurfaceControl.OnJankDataListenerRegistration mJankDataListenerRegistration;
private long mBeginVsyncId = INVALID_ID;
private long mEndVsyncId = INVALID_ID;
private boolean mMetricsFinalized;
@@ -316,7 +317,8 @@
Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, name, name, (int) mBeginVsyncId);
markEvent("FT#beginVsync", mBeginVsyncId);
markEvent("FT#layerId", mSurfaceControl.getLayerId());
- mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
+ mJankDataListenerRegistration =
+ mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
if (!mSurfaceOnly) {
mRendererWrapper.addObserver(mObserver);
}
@@ -342,6 +344,10 @@
markEvent("FT#endVsync", mEndVsyncId);
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, name, (int) mBeginVsyncId);
+ if (mJankDataListenerRegistration != null) {
+ mJankDataListenerRegistration.removeAfter(mEndVsyncId);
+ }
+
// We don't remove observer here,
// will remove it when all the frame metrics in this duration are called back.
// See onFrameMetricsAvailable for the logic of removing the observer.
@@ -358,6 +364,9 @@
// Send a flush jank data transaction.
if (mSurfaceControl != null && mSurfaceControl.isValid()) {
SurfaceControl.Transaction.sendSurfaceFlushJankData(mSurfaceControl);
+ if (mJankDataListenerRegistration != null) {
+ mJankDataListenerRegistration.flush();
+ }
}
long delay;
@@ -680,7 +689,10 @@
@VisibleForTesting
@UiThread
public void removeObservers() {
- mSurfaceControlWrapper.removeJankStatsListener(this);
+ if (mJankDataListenerRegistration != null) {
+ mJankDataListenerRegistration.release();
+ mJankDataListenerRegistration = null;
+ }
if (!mSurfaceOnly) {
// HWUI part.
mRendererWrapper.removeObserver(mObserver);
@@ -796,14 +808,10 @@
}
public static class SurfaceControlWrapper {
-
- public void addJankStatsListener(SurfaceControl.OnJankDataListener listener,
- SurfaceControl surfaceControl) {
- SurfaceControl.addJankDataListener(listener, surfaceControl);
- }
-
- public void removeJankStatsListener(SurfaceControl.OnJankDataListener listener) {
- SurfaceControl.removeJankDataListener(listener);
+ /** adds the jank listener to the given surface */
+ public SurfaceControl.OnJankDataListenerRegistration addJankStatsListener(
+ SurfaceControl.OnJankDataListener listener, SurfaceControl surfaceControl) {
+ return surfaceControl.addJankDataListener(listener);
}
}
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/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 2316f4c..b8fd3d0 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -17,9 +17,12 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "Camera-JNI"
+#include <android/content/AttributionSourceState.h>
+#include <android_os_Parcel.h>
#include <android_runtime/android_graphics_SurfaceTexture.h>
#include <android_runtime/android_view_Surface.h>
#include <binder/IMemory.h>
+#include <binder/Parcel.h>
#include <camera/Camera.h>
#include <camera/StringUtils.h>
#include <cutils/properties.h>
@@ -523,22 +526,45 @@
}
}
-static jint android_hardware_Camera_getNumberOfCameras(JNIEnv *env, jobject thiz, jint deviceId,
+static bool attributionSourceStateForJavaParcel(JNIEnv *env, jobject jClientAttributionParcel,
+ AttributionSourceState &clientAttribution) {
+ const Parcel *clientAttributionParcel = parcelForJavaObject(env, jClientAttributionParcel);
+ if (clientAttribution.readFromParcel(clientAttributionParcel) != ::android::OK) {
+ jniThrowRuntimeException(env, "Fail to unparcel AttributionSourceState");
+ return false;
+ }
+ clientAttribution.uid = Camera::USE_CALLING_UID;
+ clientAttribution.pid = Camera::USE_CALLING_PID;
+ return true;
+}
+
+static jint android_hardware_Camera_getNumberOfCameras(JNIEnv *env, jobject thiz,
+ jobject jClientAttributionParcel,
jint devicePolicy) {
- return Camera::getNumberOfCameras(deviceId, devicePolicy);
+ AttributionSourceState clientAttribution;
+ if (!attributionSourceStateForJavaParcel(env, jClientAttributionParcel, clientAttribution)) {
+ return 0;
+ }
+ return Camera::getNumberOfCameras(clientAttribution, devicePolicy);
}
static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, jint cameraId,
- jint rotationOverride, jint deviceId,
+ jint rotationOverride,
+ jobject jClientAttributionParcel,
jint devicePolicy, jobject info_obj) {
+ AttributionSourceState clientAttribution;
+ if (!attributionSourceStateForJavaParcel(env, jClientAttributionParcel, clientAttribution)) {
+ return;
+ }
+
CameraInfo cameraInfo;
- if (cameraId >= Camera::getNumberOfCameras(deviceId, devicePolicy) || cameraId < 0) {
+ if (cameraId >= Camera::getNumberOfCameras(clientAttribution, devicePolicy) || cameraId < 0) {
ALOGE("%s: Unknown camera ID %d", __FUNCTION__, cameraId);
jniThrowRuntimeException(env, "Unknown camera ID");
return;
}
- status_t rc = Camera::getCameraInfo(cameraId, rotationOverride, deviceId, devicePolicy,
+ status_t rc = Camera::getCameraInfo(cameraId, rotationOverride, clientAttribution, devicePolicy,
&cameraInfo);
if (rc != NO_ERROR) {
jniThrowRuntimeException(env, "Fail to get camera info");
@@ -557,9 +583,14 @@
// connect to camera service
static jint android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
jint cameraId, jstring clientPackageName,
- jint rotationOverride,
- jboolean forceSlowJpegMode, jint deviceId,
+ jint rotationOverride, jboolean forceSlowJpegMode,
+ jobject jClientAttributionParcel,
jint devicePolicy) {
+ AttributionSourceState clientAttribution;
+ if (!attributionSourceStateForJavaParcel(env, jClientAttributionParcel, clientAttribution)) {
+ return -EACCES;
+ }
+
// Convert jstring to String16
const char16_t *rawClientName = reinterpret_cast<const char16_t*>(
env->GetStringChars(clientPackageName, NULL));
@@ -569,10 +600,8 @@
reinterpret_cast<const jchar*>(rawClientName));
int targetSdkVersion = android_get_application_target_sdk_version();
- sp<Camera> camera =
- Camera::connect(cameraId, clientName, Camera::USE_CALLING_UID, Camera::USE_CALLING_PID,
- targetSdkVersion, rotationOverride, forceSlowJpegMode, deviceId,
- devicePolicy);
+ sp<Camera> camera = Camera::connect(cameraId, clientName, targetSdkVersion, rotationOverride,
+ forceSlowJpegMode, clientAttribution, devicePolicy);
if (camera == NULL) {
return -EACCES;
}
@@ -600,7 +629,7 @@
// Update default display orientation in case the sensor is reverse-landscape
CameraInfo cameraInfo;
- status_t rc = Camera::getCameraInfo(cameraId, rotationOverride, deviceId, devicePolicy,
+ status_t rc = Camera::getCameraInfo(cameraId, rotationOverride, clientAttribution, devicePolicy,
&cameraInfo);
if (rc != NO_ERROR) {
ALOGE("%s: getCameraInfo error: %d", __FUNCTION__, rc);
@@ -1056,10 +1085,11 @@
//-------------------------------------------------
static const JNINativeMethod camMethods[] = {
- {"_getNumberOfCameras", "(II)I", (void *)android_hardware_Camera_getNumberOfCameras},
- {"_getCameraInfo", "(IIIILandroid/hardware/Camera$CameraInfo;)V",
+ {"_getNumberOfCameras", "(Landroid/os/Parcel;I)I",
+ (void *)android_hardware_Camera_getNumberOfCameras},
+ {"_getCameraInfo", "(IILandroid/os/Parcel;ILandroid/hardware/Camera$CameraInfo;)V",
(void *)android_hardware_Camera_getCameraInfo},
- {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;IZII)I",
+ {"native_setup", "(Ljava/lang/Object;ILjava/lang/String;IZLandroid/os/Parcel;I)I",
(void *)android_hardware_Camera_native_setup},
{"native_release", "()V", (void *)android_hardware_Camera_release},
{"setPreviewSurface", "(Landroid/view/Surface;)V",
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 5365838..9ce7658 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -22,6 +22,7 @@
#include <android/graphics/properties.h>
#include <android/graphics/region.h>
#include <android/gui/BnWindowInfosReportedListener.h>
+#include <android/gui/JankData.h>
#include <android/hardware/display/IDeviceProductInfoConstants.h>
#include <android/os/IInputConstants.h>
#include <android_runtime/AndroidRuntime.h>
@@ -2062,11 +2063,13 @@
env->DeleteWeakGlobalRef(mOnJankDataListenerWeak);
}
- void onJankDataAvailable(const std::vector<JankData>& jankData) {
+ bool onJankDataAvailable(const std::vector<gui::JankData>& jankData) override {
JNIEnv* env = getEnv();
jobject target = env->NewLocalRef(mOnJankDataListenerWeak);
- if (target == nullptr) return;
+ if (target == nullptr) {
+ return false;
+ }
jobjectArray jJankDataArray = env->NewObjectArray(jankData.size(),
gJankDataClassInfo.clazz, nullptr);
@@ -2082,6 +2085,8 @@
jJankDataArray);
env->DeleteLocalRef(jJankDataArray);
env->DeleteLocalRef(target);
+
+ return true;
}
private:
@@ -2096,29 +2101,49 @@
jobject mOnJankDataListenerWeak;
};
-static void nativeAddJankDataListener(JNIEnv* env, jclass clazz,
- jlong jankDataCallbackListenerPtr,
- jlong nativeSurfaceControl) {
+static jlong nativeCreateJankDataListenerWrapper(JNIEnv* env, jclass clazz,
+ jlong nativeSurfaceControl, jobject listener) {
sp<SurfaceControl> surface(reinterpret_cast<SurfaceControl *>(nativeSurfaceControl));
if (surface == nullptr) {
+ return 0;
+ }
+
+ sp<JankDataListenerWrapper> wrapper = sp<JankDataListenerWrapper>::make(env, listener);
+ if (wrapper->addListener(std::move(surface)) != OK) {
+ return 0;
+ }
+
+ wrapper->incStrong((void*)nativeCreateJankDataListenerWrapper);
+ return reinterpret_cast<jlong>(wrapper.get());
+}
+
+static void destroyJankDatalistenerWrapper(void* ptr) {
+ JankDataListenerWrapper* wrapper = reinterpret_cast<JankDataListenerWrapper*>(ptr);
+ if (wrapper == nullptr) {
return;
}
- sp<JankDataListenerWrapper> wrapper =
- reinterpret_cast<JankDataListenerWrapper*>(jankDataCallbackListenerPtr);
- TransactionCompletedListener::getInstance()->addJankListener(wrapper, surface);
+ wrapper->decStrong((void*)nativeCreateJankDataListenerWrapper);
}
-static void nativeRemoveJankDataListener(JNIEnv* env, jclass clazz,
- jlong jankDataCallbackListenerPtr) {
- sp<JankDataListenerWrapper> wrapper =
- reinterpret_cast<JankDataListenerWrapper*>(jankDataCallbackListenerPtr);
- TransactionCompletedListener::getInstance()->removeJankListener(wrapper);
+static jlong nativeGetJankDataListenerWrapperFinalizer() {
+ return reinterpret_cast<jlong>(&destroyJankDatalistenerWrapper);
}
-static jlong nativeCreateJankDataListenerWrapper(JNIEnv* env, jclass clazz,
- jobject jankDataListenerObject) {
- return reinterpret_cast<jlong>(
- new JankDataListenerWrapper(env, jankDataListenerObject));
+static void nativeFlushJankData(JNIEnv* env, jclass clazz, jlong listener) {
+ sp<JankDataListenerWrapper> wrapper = reinterpret_cast<JankDataListenerWrapper*>(listener);
+ if (wrapper == nullptr) {
+ return;
+ }
+ wrapper->flushJankData();
+}
+
+static void nativeRemoveJankDataListener(JNIEnv* env, jclass clazz, jlong listener,
+ jlong afterVsync) {
+ sp<JankDataListenerWrapper> wrapper = reinterpret_cast<JankDataListenerWrapper*>(listener);
+ if (wrapper == nullptr) {
+ return;
+ }
+ wrapper->removeListener(afterVsync);
}
static jint nativeGetGPUContextPriority(JNIEnv* env, jclass clazz) {
@@ -2436,12 +2461,14 @@
(void*)nativeRemoveCurrentInputFocus},
{"nativeSetFrameTimelineVsync", "(JJ)V",
(void*)nativeSetFrameTimelineVsync },
- {"nativeAddJankDataListener", "(JJ)V",
- (void*)nativeAddJankDataListener },
- {"nativeRemoveJankDataListener", "(J)V",
+ {"nativeFlushJankData", "(J)V",
+ (void*)nativeFlushJankData },
+ {"nativeRemoveJankDataListener", "(JJ)V",
(void*)nativeRemoveJankDataListener },
- {"nativeCreateJankDataListenerWrapper", "(Landroid/view/SurfaceControl$OnJankDataListener;)J",
+ {"nativeCreateJankDataListenerWrapper", "(JLandroid/view/SurfaceControl$OnJankDataListener;)J",
(void*)nativeCreateJankDataListenerWrapper },
+ {"nativeGetJankDataListenerWrapperFinalizer", "()J",
+ (void*)nativeGetJankDataListenerWrapperFinalizer },
{"nativeGetGPUContextPriority", "()I",
(void*)nativeGetGPUContextPriority },
{"nativeSetTransformHint", "(JI)V",
diff --git a/core/proto/android/content/res/color_state_list.proto b/core/proto/android/content/res/color_state_list.proto
new file mode 100644
index 0000000..3d0d8a8
--- /dev/null
+++ b/core/proto/android/content/res/color_state_list.proto
@@ -0,0 +1,36 @@
+/*
+ * 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 optional 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.
+ */
+
+syntax = "proto2";
+
+option java_multiple_files = true;
+
+package android.content.res;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+/**
+ * An android.content.res.ColorStateList object.
+ */
+message ColorStateListProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+ repeated StateSpec state_specs = 1;
+ repeated int32 colors = 2 [packed = true];
+
+ message StateSpec {
+ repeated int32 state = 1 [packed = true];
+ }
+}
diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto
index d24da03..f08ea1b 100644
--- a/core/proto/android/widget/remoteviews.proto
+++ b/core/proto/android/widget/remoteviews.proto
@@ -51,6 +51,26 @@
optional RemoteViewsProto landscape_remoteviews = 11;
optional bool is_root = 12;
optional bool has_draw_instructions = 13;
+ repeated bytes bitmap_cache = 14;
+ optional RemoteCollectionCache remote_collection_cache = 15;
+
+ message RemoteCollectionCache {
+ message Entry {
+ optional int64 id = 1;
+ optional string uri = 2;
+ optional RemoteCollectionItems items = 3;
+ }
+
+ repeated Entry entries = 1;
+ }
+
+ message RemoteCollectionItems {
+ repeated int64 ids = 1 [packed = true];
+ repeated RemoteViewsProto views = 2;
+ optional bool has_stable_ids = 3;
+ optional int32 view_type_count = 4;
+ optional bool attached = 5;
+ }
}
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 37412a0..f5bb554 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -480,17 +480,17 @@
<!-- Colors used in Android system, from design system.
These values can be overlaid at runtime by OverlayManager RROs. -->
- <color name="system_primary_container_light">#5E73A9</color>
- <color name="system_on_primary_container_light">#FFFFFF</color>
- <color name="system_primary_light">#2A4174</color>
+ <color name="system_primary_container_light">#D9E2FF</color>
+ <color name="system_on_primary_container_light">#001945</color>
+ <color name="system_primary_light">#475D92</color>
<color name="system_on_primary_light">#FFFFFF</color>
- <color name="system_secondary_container_light">#6E7488</color>
- <color name="system_on_secondary_container_light">#FFFFFF</color>
- <color name="system_secondary_light">#3C4255</color>
+ <color name="system_secondary_container_light">#DCE2F9</color>
+ <color name="system_on_secondary_container_light">#151B2C</color>
+ <color name="system_secondary_light">#575E71</color>
<color name="system_on_secondary_light">#FFFFFF</color>
- <color name="system_tertiary_container_light">#8A6A89</color>
- <color name="system_on_tertiary_container_light">#FFFFFF</color>
- <color name="system_tertiary_light">#553A55</color>
+ <color name="system_tertiary_container_light">#FDD7FA</color>
+ <color name="system_on_tertiary_container_light">#2A122C</color>
+ <color name="system_tertiary_light">#725572</color>
<color name="system_on_tertiary_light">#FFFFFF</color>
<color name="system_background_light">#FAF8FF</color>
<color name="system_on_background_light">#1A1B20</color>
@@ -504,17 +504,17 @@
<color name="system_surface_bright_light">#FAF8FF</color>
<color name="system_surface_dim_light">#DAD9E0</color>
<color name="system_surface_variant_light">#E1E2EC</color>
- <color name="system_on_surface_variant_light">#40434B</color>
- <color name="system_outline_light">#5D5F67</color>
- <color name="system_outline_variant_light">#797A83</color>
- <color name="system_error_light">#8C0009</color>
+ <color name="system_on_surface_variant_light">#44464F</color>
+ <color name="system_outline_light">#757780</color>
+ <color name="system_outline_variant_light">#C5C6D0</color>
+ <color name="system_error_light">#BA1A1A</color>
<color name="system_on_error_light">#FFFFFF</color>
- <color name="system_error_container_light">#DA342E</color>
- <color name="system_on_error_container_light">#FFFFFF</color>
+ <color name="system_error_container_light">#FFDAD6</color>
+ <color name="system_on_error_container_light">#410002</color>
<color name="system_control_activated_light">#D9E2FF</color>
<color name="system_control_normal_light">#44464F</color>
<color name="system_control_highlight_light">#000000</color>
- <color name="system_text_primary_inverse_light">#E2E2E9</color>
+<color name="system_text_primary_inverse_light">#E2E2E9</color>
<color name="system_text_secondary_and_tertiary_inverse_light">#C5C6D0</color>
<color name="system_text_primary_inverse_disable_only_light">#E2E2E9</color>
<color name="system_text_secondary_and_tertiary_inverse_disabled_light">#E2E2E9</color>
@@ -524,22 +524,22 @@
<color name="system_palette_key_color_tertiary_light">#8C6D8C</color>
<color name="system_palette_key_color_neutral_light">#76777D</color>
<color name="system_palette_key_color_neutral_variant_light">#757780</color>
- <color name="system_primary_container_dark">#7A90C8</color>
- <color name="system_on_primary_container_dark">#000000</color>
- <color name="system_primary_dark">#B7CAFF</color>
- <color name="system_on_primary_dark">#00143B</color>
- <color name="system_secondary_container_dark">#8A90A5</color>
- <color name="system_on_secondary_container_dark">#000000</color>
- <color name="system_secondary_dark">#C4CAE1</color>
- <color name="system_on_secondary_dark">#0F1626</color>
- <color name="system_tertiary_container_dark">#A886A6</color>
- <color name="system_on_tertiary_container_dark">#000000</color>
- <color name="system_tertiary_dark">#E4BFE2</color>
- <color name="system_on_tertiary_dark">#240D26</color>
+ <color name="system_primary_container_dark">#2F4578</color>
+ <color name="system_on_primary_container_dark">#D9E2FF</color>
+ <color name="system_primary_dark">#B0C6FF</color>
+ <color name="system_on_primary_dark">#152E60</color>
+ <color name="system_secondary_container_dark">#404659</color>
+ <color name="system_on_secondary_container_dark">#DCE2F9</color>
+ <color name="system_secondary_dark">#C0C6DC</color>
+ <color name="system_on_secondary_dark">#2A3042</color>
+ <color name="system_tertiary_container_dark">#593D59</color>
+ <color name="system_on_tertiary_container_dark">#FDD7FA</color>
+ <color name="system_tertiary_dark">#E0BBDD</color>
+ <color name="system_on_tertiary_dark">#412742</color>
<color name="system_background_dark">#121318</color>
<color name="system_on_background_dark">#E2E2E9</color>
<color name="system_surface_dark">#121318</color>
- <color name="system_on_surface_dark">#FCFAFF</color>
+ <color name="system_on_surface_dark">#E2E2E9</color>
<color name="system_surface_container_low_dark">#1A1B20</color>
<color name="system_surface_container_lowest_dark">#0C0E13</color>
<color name="system_surface_container_dark">#1E1F25</color>
@@ -548,13 +548,13 @@
<color name="system_surface_bright_dark">#38393F</color>
<color name="system_surface_dim_dark">#121318</color>
<color name="system_surface_variant_dark">#44464F</color>
- <color name="system_on_surface_variant_dark">#C9CAD4</color>
- <color name="system_outline_dark">#A1A2AC</color>
- <color name="system_outline_variant_dark">#81838C</color>
- <color name="system_error_dark">#FFBAB1</color>
- <color name="system_on_error_dark">#370001</color>
- <color name="system_error_container_dark">#FF5449</color>
- <color name="system_on_error_container_dark">#000000</color>
+ <color name="system_on_surface_variant_dark">#C5C6D0</color>
+ <color name="system_outline_dark">#8F9099</color>
+ <color name="system_outline_variant_dark">#44464F</color>
+ <color name="system_error_dark">#FFB4AB</color>
+ <color name="system_on_error_dark">#690005</color>
+ <color name="system_error_container_dark">#93000A</color>
+ <color name="system_on_error_container_dark">#FFDAD6</color>
<color name="system_control_activated_dark">#2F4578</color>
<color name="system_control_normal_dark">#C5C6D0</color>
<color name="system_control_highlight_dark">#FFFFFF</color>
@@ -568,63 +568,63 @@
<color name="system_palette_key_color_tertiary_dark">#8C6D8C</color>
<color name="system_palette_key_color_neutral_dark">#76777D</color>
<color name="system_palette_key_color_neutral_variant_dark">#757780</color>
- <color name="system_primary_fixed">#5E73A9</color>
- <color name="system_primary_fixed_dim">#455B8F</color>
- <color name="system_on_primary_fixed">#FFFFFF</color>
- <color name="system_on_primary_fixed_variant">#FFFFFF</color>
- <color name="system_secondary_fixed">#6E7488</color>
- <color name="system_secondary_fixed_dim">#555C6F</color>
- <color name="system_on_secondary_fixed">#FFFFFF</color>
- <color name="system_on_secondary_fixed_variant">#FFFFFF</color>
- <color name="system_tertiary_fixed">#8A6A89</color>
- <color name="system_tertiary_fixed_dim">#705270</color>
- <color name="system_on_tertiary_fixed">#FFFFFF</color>
- <color name="system_on_tertiary_fixed_variant">#FFFFFF</color>
+ <color name="system_primary_fixed">#D9E2FF</color>
+ <color name="system_primary_fixed_dim">#B0C6FF</color>
+ <color name="system_on_primary_fixed">#001945</color>
+ <color name="system_on_primary_fixed_variant">#2F4578</color>
+ <color name="system_secondary_fixed">#DCE2F9</color>
+ <color name="system_secondary_fixed_dim">#C0C6DC</color>
+ <color name="system_on_secondary_fixed">#151B2C</color>
+ <color name="system_on_secondary_fixed_variant">#404659</color>
+ <color name="system_tertiary_fixed">#FDD7FA</color>
+ <color name="system_tertiary_fixed_dim">#E0BBDD</color>
+ <color name="system_on_tertiary_fixed">#2A122C</color>
+ <color name="system_on_tertiary_fixed_variant">#593D59</color>
<!--Colors used in Android system, from design system. These values can be overlaid at runtime
by OverlayManager RROs.-->
<color name="system_widget_background_light">#EEF0FF</color>
- <color name="system_clock_hour_light">#1D2435</color>
- <color name="system_clock_minute_light">#20386A</color>
- <color name="system_clock_second_light">#000000</color>
- <color name="system_theme_app_light">#2F4578</color>
- <color name="system_on_theme_app_light">#D6DFFF</color>
+ <color name="system_clock_hour_light">#373D50</color>
+ <color name="system_clock_minute_light">#3D5487</color>
+ <color name="system_clock_second_light">#4F659A</color>
+ <color name="system_theme_app_light">#D9E2FF</color>
+ <color name="system_on_theme_app_light">#475D92</color>
<color name="system_theme_app_ring_light">#94AAE4</color>
- <color name="system_theme_notif_light">#FDD7FA</color>
- <color name="system_brand_a_light">#3A5084</color>
+ <color name="system_theme_notif_light">#E0BBDD</color>
+ <color name="system_brand_a_light">#475D92</color>
<color name="system_brand_b_light">#6E7488</color>
- <color name="system_brand_c_light">#6076AC</color>
- <color name="system_brand_d_light">#8C6D8C</color>
+ <color name="system_brand_c_light">#5E73A9</color>
+ <color name="system_brand_d_light">#8A6A89</color>
<color name="system_under_surface_light">#000000</color>
- <color name="system_shade_active_light">#D9E2FF</color>
+<color name="system_shade_active_light">#D9E2FF</color>
<color name="system_on_shade_active_light">#152E60</color>
<color name="system_on_shade_active_variant_light">#2F4578</color>
<color name="system_shade_inactive_light">#2F3036</color>
<color name="system_on_shade_inactive_light">#E1E2EC</color>
<color name="system_on_shade_inactive_variant_light">#C5C6D0</color>
<color name="system_shade_disabled_light">#0C0E13</color>
- <color name="system_overview_background_light">#50525A</color>
+ <color name="system_overview_background_light">#C5C6D0</color>
<color name="system_widget_background_dark">#152E60</color>
- <color name="system_clock_hour_dark">#9AA0B6</color>
- <color name="system_clock_minute_dark">#D8E1FF</color>
- <color name="system_clock_second_dark">#FFFFFF</color>
- <color name="system_theme_app_dark">#D9E2FF</color>
- <color name="system_on_theme_app_dark">#304679</color>
+ <color name="system_clock_hour_dark">#8A90A5</color>
+ <color name="system_clock_minute_dark">#D9E2FF</color>
+ <color name="system_clock_second_dark">#B0C6FF</color>
+ <color name="system_theme_app_dark">#2F4578</color>
+ <color name="system_on_theme_app_dark">#B0C6FF</color>
<color name="system_theme_app_ring_dark">#94AAE4</color>
- <color name="system_theme_notif_dark">#E0BBDD</color>
- <color name="system_brand_a_dark">#90A6DF</color>
- <color name="system_brand_b_dark">#A4ABC1</color>
+ <color name="system_theme_notif_dark">#FDD7FA</color>
+ <color name="system_brand_a_dark">#B0C6FF</color>
+ <color name="system_brand_b_dark">#DCE2F9</color>
<color name="system_brand_c_dark">#7A90C8</color>
- <color name="system_brand_d_dark">#A886A6</color>
+ <color name="system_brand_d_dark">#FDD7FA</color>
<color name="system_under_surface_dark">#000000</color>
- <color name="system_shade_active_dark">#D9E2FF</color>
+<color name="system_shade_active_dark">#D9E2FF</color>
<color name="system_on_shade_active_dark">#001945</color>
<color name="system_on_shade_active_variant_dark">#2F4578</color>
<color name="system_shade_inactive_dark">#2F3036</color>
<color name="system_on_shade_inactive_dark">#E1E2EC</color>
<color name="system_on_shade_inactive_variant_dark">#C5C6D0</color>
<color name="system_shade_disabled_dark">#0C0E13</color>
- <color name="system_overview_background_dark">#C5C6D0</color>
+ <color name="system_overview_background_dark">#50525A</color>
<!-- Accessibility shortcut icon background color -->
<color name="accessibility_feature_background">#5F6368</color> <!-- Google grey 700 -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index fa93e76..e3f9187 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -722,6 +722,9 @@
<!-- label for screenshot item in power menu [CHAR LIMIT=24]-->
<string name="global_action_screenshot">Screenshot</string>
+ <!-- description for mandatory biometrics prompt -->
+ <string name="identity_check_biometric_prompt_description">This is needed since Identity Check is on</string>
+
<!-- Take bug report menu title [CHAR LIMIT=30] -->
<string name="bugreport_title">Bug report</string>
<!-- Message in bugreport dialog describing what it does [CHAR LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index bdcf13c..6e5e106 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1918,6 +1918,7 @@
<java-symbol type="string" name="global_action_voice_assist" />
<java-symbol type="string" name="global_action_assist" />
<java-symbol type="string" name="global_action_screenshot" />
+ <java-symbol type="string" name="identity_check_biometric_prompt_description" />
<java-symbol type="string" name="invalidPuk" />
<java-symbol type="string" name="lockscreen_carrier_default" />
<java-symbol type="style" name="Animation.LockScreen" />
diff --git a/core/res/res/xml/sms_short_codes.xml b/core/res/res/xml/sms_short_codes.xml
index 67cceb5..581dee5 100644
--- a/core/res/res/xml/sms_short_codes.xml
+++ b/core/res/res/xml/sms_short_codes.xml
@@ -185,6 +185,9 @@
https://www.itu.int/dms_pub/itu-t/oth/02/02/T020200006B0001PDFE.pdf -->
<shortcode country="it" pattern="\\d{5}" premium="44[0-4]\\d{2}|47[0-4]\\d{2}|48[0-4]\\d{2}|44[5-9]\\d{4}|47[5-9]\\d{4}|48[5-9]\\d{4}|455\\d{2}|499\\d{2}" free="116\\d{3}|4112503|40\\d{0,12}" standard="430\\d{2}|431\\d{2}|434\\d{4}|435\\d{4}|439\\d{7}" />
+ <!-- Jordan: 1-5 digits (standard system default, not country specific) -->
+ <shortcode country="jo" pattern="\\d{1,5}" free="99066" />
+
<!-- Japan: 8083 used by SOFTBANK_DCB_2 -->
<shortcode country="jp" pattern="\\d{1,5}" free="8083" />
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/graphics/ColorStateListTest.java b/core/tests/coretests/src/android/graphics/ColorStateListTest.java
index a3d52ea..ab41bd0 100644
--- a/core/tests/coretests/src/android/graphics/ColorStateListTest.java
+++ b/core/tests/coretests/src/android/graphics/ColorStateListTest.java
@@ -19,6 +19,8 @@
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.test.AndroidTestCase;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
import androidx.test.filters.SmallTest;
@@ -49,6 +51,15 @@
}
@SmallTest
+ public void testStateIsInList_proto() throws Exception {
+ ColorStateList colorStateList = recreateFromProto(
+ mResources.getColorStateList(R.color.color1));
+ int[] focusedState = {android.R.attr.state_focused};
+ int focusColor = colorStateList.getColorForState(focusedState, R.color.failColor);
+ assertEquals(mResources.getColor(R.color.testcolor1), focusColor);
+ }
+
+ @SmallTest
public void testEmptyState() throws Exception {
ColorStateList colorStateList = mResources.getColorStateList(R.color.color1);
int[] emptyState = {};
@@ -57,6 +68,15 @@
}
@SmallTest
+ public void testEmptyState_proto() throws Exception {
+ ColorStateList colorStateList = recreateFromProto(
+ mResources.getColorStateList(R.color.color1));
+ int[] emptyState = {};
+ int defaultColor = colorStateList.getColorForState(emptyState, mFailureColor);
+ assertEquals(mResources.getColor(R.color.testcolor2), defaultColor);
+ }
+
+ @SmallTest
public void testGetColor() throws Exception {
int defaultColor = mResources.getColor(R.color.color1);
assertEquals(mResources.getColor(R.color.testcolor2), defaultColor);
@@ -73,4 +93,11 @@
int defaultColor = mResources.getColor(R.color.color_with_lstar);
assertEquals(mResources.getColor(R.color.testcolor3), defaultColor);
}
+
+ private ColorStateList recreateFromProto(ColorStateList colorStateList) throws Exception {
+ ProtoOutputStream out = new ProtoOutputStream();
+ colorStateList.writeToProto(out);
+ ProtoInputStream in = new ProtoInputStream(out.getBytes());
+ return ColorStateList.createFromProto(in);
+ }
}
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/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index 1a7117e..499caf5 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -29,6 +29,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
@@ -83,6 +84,7 @@
private ChoreographerWrapper mChoreographer;
private StatsLogWrapper mStatsLog;
private ArgumentCaptor<OnJankDataListener> mListenerCapture;
+ private SurfaceControl.OnJankDataListenerRegistration mJankStatsRegistration;
private SurfaceControl mSurfaceControl;
private FrameTracker.FrameTrackerListener mTrackerListener;
private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
@@ -107,10 +109,11 @@
mSurfaceControlWrapper = mock(SurfaceControlWrapper.class);
mListenerCapture = ArgumentCaptor.forClass(OnJankDataListener.class);
- doNothing().when(mSurfaceControlWrapper).addJankStatsListener(
+ mJankStatsRegistration = mock(SurfaceControl.OnJankDataListenerRegistration.class);
+ doReturn(mJankStatsRegistration).when(mSurfaceControlWrapper).addJankStatsListener(
mListenerCapture.capture(), any());
- doNothing().when(mSurfaceControlWrapper).removeJankStatsListener(
- mListenerCapture.capture());
+ doNothing().when(mJankStatsRegistration).flush();
+ doNothing().when(mJankStatsRegistration).removeAfter(anyLong());
mChoreographer = mock(ChoreographerWrapper.class);
mStatsLog = mock(StatsLogWrapper.class);
@@ -483,7 +486,7 @@
// an extra frame to trigger finish
sendFrame(tracker, JANK_NONE, 103L);
- verify(mSurfaceControlWrapper).removeJankStatsListener(any());
+ verify(mJankStatsRegistration).removeAfter(anyLong());
verify(mTrackerListener).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
@@ -520,7 +523,7 @@
// an extra frame to trigger finish
sendFrame(tracker, JANK_NONE, 103L);
- verify(mSurfaceControlWrapper).removeJankStatsListener(any());
+ verify(mJankStatsRegistration).removeAfter(anyLong());
verify(mTrackerListener, never()).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
@@ -557,7 +560,7 @@
// janky frame, should be ignored, trigger finish
sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L);
- verify(mSurfaceControlWrapper).removeJankStatsListener(any());
+ verify(mJankStatsRegistration).removeAfter(anyLong());
verify(mTrackerListener, never()).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
@@ -589,7 +592,7 @@
tracker.end(FrameTracker.REASON_END_NORMAL);
sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 106L);
sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 107L);
- verify(mSurfaceControlWrapper).removeJankStatsListener(any());
+ verify(mJankStatsRegistration).removeAfter(anyLong());
verify(mTrackerListener).triggerPerfetto(any());
verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED),
eq(42), /* displayId */
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 68095e5..f763984 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -183,8 +183,6 @@
doNothing().when(viewRoot).removeSurfaceChangedCallback(any());
SurfaceControlWrapper surfaceControl = mock(SurfaceControlWrapper.class);
- doNothing().when(surfaceControl).addJankStatsListener(any(), any());
- doNothing().when(surfaceControl).removeJankStatsListener(any());
final ChoreographerWrapper choreographer = mock(ChoreographerWrapper.class);
doReturn(SystemClock.elapsedRealtime()).when(choreographer).getVsyncId();
diff --git a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
index 06d888b..7ffc7b2 100644
--- a/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/MonotonicClockTest.java
@@ -18,7 +18,6 @@
import static com.google.common.truth.Truth.assertThat;
-import android.platform.test.annotations.IgnoreUnderRavenwood;
import android.platform.test.ravenwood.RavenwoodRule;
import androidx.test.filters.SmallTest;
@@ -77,7 +76,6 @@
}
@Test
- @IgnoreUnderRavenwood(reason = "b/321832617")
public void corruptedFile() throws IOException {
// Create an invalid binary XML file to cause IOException: "Unexpected magic number"
try (FileWriter w = new FileWriter(mFile)) {
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 66b47da..c573cf4a 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -92,5 +92,6 @@
<permission name="android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS" />
<permission name="android.permission.CONTROL_UI_TRACING" />
<permission name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND" />
+ <permission name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED" />
</privapp-permissions>
</permissions>
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/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
index 7d5f9cd..5fe3f2a 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml
@@ -14,88 +14,100 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:id="@+id/maximize_menu"
- style="?android:attr/buttonBarStyle"
android:layout_width="@dimen/desktop_mode_maximize_menu_width"
android:layout_height="@dimen/desktop_mode_maximize_menu_height"
- android:orientation="horizontal"
- android:gravity="center"
- android:padding="16dp"
android:background="@drawable/desktop_mode_maximize_menu_background"
android:elevation="1dp">
<LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical">
+ android:id="@+id/container"
+ android:layout_width="@dimen/desktop_mode_maximize_menu_width"
+ android:layout_height="@dimen/desktop_mode_maximize_menu_height"
+ android:orientation="horizontal"
+ android:padding="16dp"
+ android:gravity="center">
- <Button
- android:layout_width="94dp"
- android:layout_height="60dp"
- android:id="@+id/maximize_menu_maximize_button"
- style="?android:attr/buttonBarButtonStyle"
- android:stateListAnimator="@null"
- android:layout_marginRight="8dp"
- android:layout_marginBottom="4dp"
- android:alpha="0"/>
-
- <TextView
- android:id="@+id/maximize_menu_maximize_window_text"
- android:layout_width="94dp"
- android:layout_height="18dp"
- android:textSize="11sp"
- android:layout_marginBottom="76dp"
- android:gravity="center"
- android:fontFamily="google-sans-text"
- android:text="@string/desktop_mode_maximize_menu_maximize_text"
- android:textColor="?androidprv:attr/materialColorOnSurface"
- android:alpha="0"/>
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:orientation="vertical">
<LinearLayout
- android:id="@+id/maximize_menu_snap_menu_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:padding="4dp"
- android:background="@drawable/desktop_mode_maximize_menu_layout_background"
- android:layout_marginBottom="4dp"
- android:alpha="0">
- <Button
- android:id="@+id/maximize_menu_snap_left_button"
- style="?android:attr/buttonBarButtonStyle"
- android:layout_width="41dp"
- android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
- android:layout_marginRight="4dp"
- android:background="@drawable/desktop_mode_maximize_menu_button_background"
- android:stateListAnimator="@null"/>
+ android:orientation="vertical">
<Button
- android:id="@+id/maximize_menu_snap_right_button"
+ android:layout_width="94dp"
+ android:layout_height="60dp"
+ android:id="@+id/maximize_menu_maximize_button"
style="?android:attr/buttonBarButtonStyle"
- android:layout_width="41dp"
- android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
- android:background="@drawable/desktop_mode_maximize_menu_button_background"
- android:stateListAnimator="@null"/>
+ android:stateListAnimator="@null"
+ android:layout_marginRight="8dp"
+ android:layout_marginBottom="4dp"
+ android:alpha="0"/>
+
+ <TextView
+ android:id="@+id/maximize_menu_maximize_window_text"
+ android:layout_width="94dp"
+ android:layout_height="18dp"
+ android:textSize="11sp"
+ android:layout_marginBottom="76dp"
+ android:gravity="center"
+ android:fontFamily="google-sans-text"
+ android:text="@string/desktop_mode_maximize_menu_maximize_text"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:alpha="0"/>
</LinearLayout>
- <TextView
- android:id="@+id/maximize_menu_snap_window_text"
- android:layout_width="94dp"
- android:layout_height="18dp"
- android:textSize="11sp"
- android:layout_marginBottom="76dp"
- android:layout_gravity="center"
- android:gravity="center"
- android:fontFamily="google-sans-text"
- android:text="@string/desktop_mode_maximize_menu_snap_text"
- android:textColor="?androidprv:attr/materialColorOnSurface"
- android:alpha="0"/>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <LinearLayout
+ android:id="@+id/maximize_menu_snap_menu_layout"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="4dp"
+ android:background="@drawable/desktop_mode_maximize_menu_layout_background"
+ android:layout_marginBottom="4dp"
+ android:alpha="0">
+ <Button
+ android:id="@+id/maximize_menu_snap_left_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="41dp"
+ android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
+ android:layout_marginRight="4dp"
+ android:background="@drawable/desktop_mode_maximize_menu_button_background"
+ android:stateListAnimator="@null"/>
+
+ <Button
+ android:id="@+id/maximize_menu_snap_right_button"
+ style="?android:attr/buttonBarButtonStyle"
+ android:layout_width="41dp"
+ android:layout_height="@dimen/desktop_mode_maximize_menu_button_height"
+ android:background="@drawable/desktop_mode_maximize_menu_button_background"
+ android:stateListAnimator="@null"/>
+ </LinearLayout>
+ <TextView
+ android:id="@+id/maximize_menu_snap_window_text"
+ android:layout_width="94dp"
+ android:layout_height="18dp"
+ android:textSize="11sp"
+ android:layout_marginBottom="76dp"
+ android:layout_gravity="center"
+ android:gravity="center"
+ android:fontFamily="google-sans-text"
+ android:text="@string/desktop_mode_maximize_menu_snap_text"
+ android:textColor="?androidprv:attr/materialColorOnSurface"
+ android:alpha="0"/>
+ </LinearLayout>
</LinearLayout>
-</LinearLayout>
+
+ <!-- Empty view intentionally placed in front of everything else and matching the menu size
+ used to monitor input events over the entire menu. -->
+ <View
+ android:id="@+id/maximize_menu_overlay"
+ android:layout_width="@dimen/desktop_mode_maximize_menu_width"
+ android:layout_height="@dimen/desktop_mode_maximize_menu_height"/>
+</FrameLayout>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 3ded7d2..bebfa90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -124,6 +124,15 @@
}
/**
+ * Limited scope callback to notify when a task is removed from the system. This signal is
+ * not synchronized with anything (or any transition), and should not be used in cases where
+ * that is necessary.
+ */
+ public interface TaskVanishedListener {
+ default void onTaskVanished(RunningTaskInfo taskInfo) {}
+ }
+
+ /**
* Callbacks for events on a task with a locus id.
*/
public interface LocusIdListener {
@@ -167,6 +176,9 @@
private final ArraySet<FocusListener> mFocusListeners = new ArraySet<>();
+ // Listeners that should be notified when a task is removed
+ private final ArraySet<TaskVanishedListener> mTaskVanishedListeners = new ArraySet<>();
+
private final Object mLock = new Object();
private StartingWindowController mStartingWindow;
@@ -409,7 +421,7 @@
}
/**
- * Removes listener.
+ * Removes a locus id listener.
*/
public void removeLocusIdListener(LocusIdListener listener) {
synchronized (mLock) {
@@ -430,7 +442,7 @@
}
/**
- * Removes listener.
+ * Removes a focus listener.
*/
public void removeFocusListener(FocusListener listener) {
synchronized (mLock) {
@@ -439,6 +451,24 @@
}
/**
+ * Adds a listener to be notified when a task vanishes.
+ */
+ public void addTaskVanishedListener(TaskVanishedListener listener) {
+ synchronized (mLock) {
+ mTaskVanishedListeners.add(listener);
+ }
+ }
+
+ /**
+ * Removes a task-vanished listener.
+ */
+ public void removeTaskVanishedListener(TaskVanishedListener listener) {
+ synchronized (mLock) {
+ mTaskVanishedListeners.remove(listener);
+ }
+ }
+
+ /**
* Returns a surface which can be used to attach overlays to the home root task
*/
@NonNull
@@ -614,6 +644,9 @@
t.apply();
ProtoLog.v(WM_SHELL_TASK_ORG, "Removing overlay surface");
}
+ for (TaskVanishedListener l : mTaskVanishedListeners) {
+ l.onTaskVanished(taskInfo);
+ }
if (!ENABLE_SHELL_TRANSITIONS && (appearedInfo.getLeash() != null)) {
// Preemptively clean up the leash only if shell transitions are not enabled
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..4ea41d5 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,
@@ -644,6 +646,7 @@
ShellInit shellInit,
ShellController shellController,
ShellCommandHandler shellCommandHandler,
+ ShellTaskOrganizer shellTaskOrganizer,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
@@ -651,8 +654,8 @@
Transitions transitions,
@ShellMainThread ShellExecutor mainExecutor) {
return new DragAndDropController(context, shellInit, shellController, shellCommandHandler,
- displayController, uiEventLogger, iconProvider, globalDragListener, transitions,
- mainExecutor);
+ shellTaskOrganizer, displayController, uiEventLogger, iconProvider,
+ globalDragListener, transitions, mainExecutor);
}
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
index 677fd5d..240cf3b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip1Module.java
@@ -212,12 +212,13 @@
@WMSingleton
@Provides
static PipMotionHelper providePipMotionHelper(Context context,
+ @ShellMainThread ShellExecutor mainExecutor,
PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer,
PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm,
PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
Optional<PipPerfHintController> pipPerfHintControllerOptional) {
- return new PipMotionHelper(context, pipBoundsState, pipTaskOrganizer,
+ return new PipMotionHelper(context, mainExecutor, pipBoundsState, pipTaskOrganizer,
menuController, pipSnapAlgorithm, pipTransitionController,
floatingContentCoordinator, pipPerfHintControllerOptional);
}
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/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index c374eb8..a4813a3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -62,6 +62,7 @@
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
import com.android.wm.shell.common.RemoteCallable;
@@ -85,6 +86,7 @@
public class DragAndDropController implements RemoteCallable<DragAndDropController>,
GlobalDragListener.GlobalDragListenerCallback,
DisplayController.OnDisplaysChangedListener,
+ ShellTaskOrganizer.TaskVanishedListener,
View.OnDragListener, ComponentCallbacks2 {
private static final String TAG = DragAndDropController.class.getSimpleName();
@@ -92,6 +94,7 @@
private final Context mContext;
private final ShellController mShellController;
private final ShellCommandHandler mShellCommandHandler;
+ private final ShellTaskOrganizer mShellTaskOrganizer;
private final DisplayController mDisplayController;
private final DragAndDropEventLogger mLogger;
private final IconProvider mIconProvider;
@@ -133,6 +136,7 @@
ShellInit shellInit,
ShellController shellController,
ShellCommandHandler shellCommandHandler,
+ ShellTaskOrganizer shellTaskOrganizer,
DisplayController displayController,
UiEventLogger uiEventLogger,
IconProvider iconProvider,
@@ -142,6 +146,7 @@
mContext = context;
mShellController = shellController;
mShellCommandHandler = shellCommandHandler;
+ mShellTaskOrganizer = shellTaskOrganizer;
mDisplayController = displayController;
mLogger = new DragAndDropEventLogger(uiEventLogger);
mIconProvider = iconProvider;
@@ -163,6 +168,7 @@
}, 0);
mShellController.addExternalInterface(KEY_EXTRA_SHELL_DRAG_AND_DROP,
this::createExternalInterface, this);
+ mShellTaskOrganizer.addTaskVanishedListener(this);
mShellCommandHandler.addDumpCallback(this::dump, this);
mGlobalDragListener.setListener(this);
}
@@ -281,6 +287,34 @@
}
@Override
+ public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+ if (taskInfo.baseIntent == null) {
+ // Invalid info
+ return;
+ }
+ // Find the active drag
+ PerDisplay pd = null;
+ for (int i = 0; i < mDisplayDropTargets.size(); i++) {
+ final PerDisplay iPd = mDisplayDropTargets.valueAt(i);
+ if (iPd.isHandlingDrag) {
+ pd = iPd;
+ break;
+ }
+ }
+ if (pd == null || !pd.isHandlingDrag) {
+ // Not currently dragging
+ return;
+ }
+
+ // Update the drag session
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Handling vanished task: id=%d component=%s", taskInfo.taskId,
+ taskInfo.baseIntent.getComponent());
+ pd.dragSession.updateRunningTask();
+ pd.dragLayout.updateSession(pd.dragSession);
+ }
+
+ @Override
public boolean onDrag(View target, DragEvent event) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
"Drag event: action=%s x=%f y=%f xOffset=%f yOffset=%f",
@@ -313,11 +347,10 @@
Slog.w(TAG, "Unexpected drag start during an active drag");
return false;
}
- // TODO(b/290391688): Also update the session data with task stack changes
pd.dragSession = new DragSession(ActivityTaskManager.getInstance(),
mDisplayController.getDisplayLayout(displayId), event.getClipData(),
event.getDragFlags());
- pd.dragSession.update();
+ pd.dragSession.initialize();
pd.activeDragCount++;
pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
setDropTargetWindowVisibility(pd, View.VISIBLE);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index a42ca19..b1882fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -84,7 +84,10 @@
private static final String TAG = DragAndDropPolicy.class.getSimpleName();
private final Context mContext;
- private final Starter mStarter;
+ // Used only for launching a fullscreen task (or as a fallback if there is no split starter)
+ private final Starter mFullscreenStarter;
+ // Used for launching tasks into splitscreen
+ private final Starter mSplitscreenStarter;
private final SplitScreenController mSplitScreen;
private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();
private final RectF mDisallowHitRegion = new RectF();
@@ -97,10 +100,12 @@
}
@VisibleForTesting
- DragAndDropPolicy(Context context, SplitScreenController splitScreen, Starter starter) {
+ DragAndDropPolicy(Context context, SplitScreenController splitScreen,
+ Starter fullscreenStarter) {
mContext = context;
mSplitScreen = splitScreen;
- mStarter = mSplitScreen != null ? mSplitScreen : starter;
+ mFullscreenStarter = fullscreenStarter;
+ mSplitscreenStarter = splitScreen;
}
/**
@@ -245,17 +250,20 @@
mSplitScreen.onDroppedToSplit(position, mLoggerSessionId);
}
+ final Starter starter = target.type == TYPE_FULLSCREEN
+ ? mFullscreenStarter
+ : mSplitscreenStarter;
if (mSession.appData != null) {
- launchApp(mSession, position);
+ launchApp(mSession, starter, position);
} else {
- launchIntent(mSession, position);
+ launchIntent(mSession, starter, position);
}
}
/**
* Launches an app provided by SysUI.
*/
- private void launchApp(DragSession session, @SplitPosition int position) {
+ private void launchApp(DragSession session, Starter starter, @SplitPosition int position) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching app data at position=%d",
position);
final ClipDescription description = session.getClipDescription();
@@ -275,11 +283,11 @@
if (isTask) {
final int taskId = session.appData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID);
- mStarter.startTask(taskId, position, opts);
+ starter.startTask(taskId, position, opts);
} else if (isShortcut) {
final String packageName = session.appData.getStringExtra(EXTRA_PACKAGE_NAME);
final String id = session.appData.getStringExtra(EXTRA_SHORTCUT_ID);
- mStarter.startShortcut(packageName, id, position, opts, user);
+ starter.startShortcut(packageName, id, position, opts, user);
} else {
final PendingIntent launchIntent =
session.appData.getParcelableExtra(EXTRA_PENDING_INTENT);
@@ -288,7 +296,7 @@
Log.e(TAG, "Expected app intent's EXTRA_USER to match pending intent user");
}
}
- mStarter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
+ starter.startIntent(launchIntent, user.getIdentifier(), null /* fillIntent */,
position, opts);
}
}
@@ -296,7 +304,7 @@
/**
* Launches an intent sender provided by an application.
*/
- private void launchIntent(DragSession session, @SplitPosition int position) {
+ private void launchIntent(DragSession session, Starter starter, @SplitPosition int position) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Launching intent at position=%d",
position);
final ActivityOptions baseActivityOpts = ActivityOptions.makeBasic();
@@ -309,7 +317,7 @@
| FLAG_ACTIVITY_MULTIPLE_TASK);
final Bundle opts = baseActivityOpts.toBundle();
- mStarter.startIntent(session.launchableIntent,
+ starter.startIntent(session.launchableIntent,
session.launchableIntent.getCreatorUserHandle().getIdentifier(),
null /* fillIntent */, position, opts);
}
@@ -420,7 +428,7 @@
@Override
public String toString() {
- return "Target {hit=" + hitRegion + " draw=" + drawRegion + "}";
+ return "Target {type=" + type + " hit=" + hitRegion + " draw=" + drawRegion + "}";
}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index 4bb10df..5df83be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -42,6 +42,7 @@
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
+import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
@@ -102,6 +103,8 @@
private boolean mIsShowing;
private boolean mHasDropped;
private DragSession mSession;
+ // The last position that was handled by the drag layout
+ private final Point mLastPosition = new Point();
@SuppressLint("WrongConstant")
public DragLayout(Context context, SplitScreenController splitScreenController,
@@ -265,6 +268,15 @@
*/
public void prepare(DragSession session, InstanceId loggerSessionId) {
mPolicy.start(session, loggerSessionId);
+ updateSession(session);
+ }
+
+ /**
+ * Updates the drag layout based on the diven drag session.
+ */
+ public void updateSession(DragSession session) {
+ // Note: The policy currently just keeps a reference to the session
+ boolean updatingExistingSession = mSession != null;
mSession = session;
mHasDropped = false;
mCurrentTarget = null;
@@ -312,6 +324,11 @@
updateDropZoneSizes(topOrLeftBounds, bottomOrRightBounds);
}
requestLayout();
+ if (updatingExistingSession) {
+ // Update targets if we are already currently dragging
+ recomputeDropTargets();
+ update(mLastPosition.x, mLastPosition.y);
+ }
}
private void updateDropZoneSizesForSingleTask() {
@@ -359,6 +376,9 @@
mDropZoneView2.setLayoutParams(dropZoneView2);
}
+ /**
+ * Shows the drag layout.
+ */
public void show() {
mIsShowing = true;
recomputeDropTargets();
@@ -384,13 +404,19 @@
* Updates the visible drop target as the user drags.
*/
public void update(DragEvent event) {
+ update((int) event.getX(), (int) event.getY());
+ }
+
+ /**
+ * Updates the visible drop target as the user drags to the given coordinates.
+ */
+ private void update(int x, int y) {
if (mHasDropped) {
return;
}
// Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the
// visibility of the current region
- DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(
- (int) event.getX(), (int) event.getY());
+ DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation(x, y);
if (mCurrentTarget != target) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target);
if (target == null) {
@@ -429,6 +455,7 @@
}
mCurrentTarget = target;
}
+ mLastPosition.set(x, y);
}
/**
@@ -436,6 +463,7 @@
*/
public void hide(DragEvent event, Runnable hideCompleteCallback) {
mIsShowing = false;
+ mLastPosition.set(-1, -1);
animateSplitContainers(false, () -> {
if (hideCompleteCallback != null) {
hideCompleteCallback.run();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
index 0addd43..41a50b1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragSession.java
@@ -30,7 +30,9 @@
import androidx.annotation.Nullable;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import java.util.List;
@@ -79,17 +81,27 @@
}
/**
- * Updates the session data based on the current state of the system.
+ * Updates the running task for this drag session.
*/
- void update() {
- List<ActivityManager.RunningTaskInfo> tasks =
+ void updateRunningTask() {
+ final List<ActivityManager.RunningTaskInfo> tasks =
mActivityTaskManager.getTasks(1, false /* filterOnlyVisibleRecents */);
if (!tasks.isEmpty()) {
final ActivityManager.RunningTaskInfo task = tasks.get(0);
runningTaskInfo = task;
runningTaskWinMode = task.getWindowingMode();
runningTaskActType = task.getActivityType();
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "Running task: id=%d component=%s", task.taskId,
+ task.baseIntent != null ? task.baseIntent.getComponent() : "null");
}
+ }
+
+ /**
+ * Updates the session data based on the current state of the system at the start of the drag.
+ */
+ void initialize() {
+ updateRunningTask();
activityInfo = mInitialDragData.getItemAt(0).getActivityInfo();
// TODO: This should technically check & respect config_supportsNonResizableMultiWindow
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index 724a130..2ccadb8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -19,6 +19,7 @@
import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.graphics.Canvas;
@@ -37,13 +38,16 @@
import androidx.annotation.Nullable;
import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
/**
* Renders a drop zone area for items being dragged.
*/
public class DropZoneView extends FrameLayout {
+ private static final boolean DEBUG_LAYOUT = false;
private static final float SPLASHSCREEN_ALPHA = 0.90f;
private static final float HIGHLIGHT_ALPHA = 1f;
private static final int MARGIN_ANIMATION_ENTER_DURATION = 400;
@@ -77,6 +81,7 @@
private int mHighlightColor;
private ObjectAnimator mBackgroundAnimator;
+ private int mTargetBackgroundColor;
private ObjectAnimator mMarginAnimator;
private float mMarginPercent;
@@ -181,6 +186,9 @@
/** Animates between highlight and splashscreen depending on current state. */
public void animateSwitch() {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "animateSwitch");
+ }
mShowingHighlight = !mShowingHighlight;
mShowingSplash = !mShowingHighlight;
final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
@@ -190,6 +198,10 @@
/** Animates the highlight indicating the zone is hovered on or not. */
public void setShowingHighlight(boolean showingHighlight) {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "setShowingHighlight: showing=%b",
+ showingHighlight);
+ }
mShowingHighlight = showingHighlight;
mShowingSplash = !mShowingHighlight;
final int newColor = mShowingHighlight ? mHighlightColor : mSplashScreenColor;
@@ -199,6 +211,10 @@
/** Animates the margins around the drop zone to show or hide. */
public void setShowingMargin(boolean visible) {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "setShowingMargin: visible=%b",
+ visible);
+ }
if (mShowingMargin != visible) {
mShowingMargin = visible;
animateMarginToState();
@@ -212,6 +228,15 @@
}
private void animateBackground(int startColor, int endColor) {
+ if (DEBUG_LAYOUT) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
+ "animateBackground: start=%s end=%s",
+ Integer.toHexString(startColor), Integer.toHexString(endColor));
+ }
+ if (endColor == mTargetBackgroundColor) {
+ // Already at, or animating to, that background color
+ return;
+ }
if (mBackgroundAnimator != null) {
mBackgroundAnimator.cancel();
}
@@ -223,6 +248,7 @@
mBackgroundAnimator.setInterpolator(FAST_OUT_SLOW_IN);
}
mBackgroundAnimator.start();
+ mTargetBackgroundColor = endColor;
}
private void animateSplashScreenIcon() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
index ce98458..93ede7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PinnedStackListenerForwarder.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.pip;
-import android.content.ComponentName;
import android.os.RemoteException;
import android.view.IPinnedTaskListener;
import android.view.WindowManagerGlobal;
@@ -70,12 +69,6 @@
}
}
- private void onActivityHidden(ComponentName componentName) {
- for (PinnedTaskListener listener : mListeners) {
- listener.onActivityHidden(componentName);
- }
- }
-
@BinderThread
private class PinnedTaskListenerImpl extends IPinnedTaskListener.Stub {
@Override
@@ -91,13 +84,6 @@
PinnedStackListenerForwarder.this.onImeVisibilityChanged(imeVisible, imeHeight);
});
}
-
- @Override
- public void onActivityHidden(ComponentName componentName) {
- mMainExecutor.execute(() -> {
- PinnedStackListenerForwarder.this.onActivityHidden(componentName);
- });
- }
}
/**
@@ -108,7 +94,5 @@
public void onMovementBoundsChanged(boolean fromImeAdjustment) {}
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {}
-
- public void onActivityHidden(ComponentName componentName) {}
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index a749019..b27c428 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,10 +16,12 @@
package com.android.wm.shell.pip;
+import android.annotation.NonNull;
import android.graphics.Rect;
import com.android.wm.shell.shared.annotations.ExternalThread;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -69,9 +71,10 @@
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
/**
- * @return {@link PipTransitionController} instance.
+ * Register {@link PipTransitionController.PipTransitionCallback} to listen on PiP transition
+ * started / finished callbacks.
*/
- default PipTransitionController getPipTransitionController() {
- return null;
- }
+ default void registerPipTransitionCallback(
+ @NonNull PipTransitionController.PipTransitionCallback callback,
+ @NonNull Executor executor) { }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index e2e1ecd..3fae370 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -423,7 +423,8 @@
});
mPipTransitionController.setPipOrganizer(this);
displayController.addDisplayWindowListener(this);
- pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+ pipTransitionController.registerPipTransitionCallback(
+ mPipTransitionCallback, mMainExecutor);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 3cae72d..f3a8fbf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -24,6 +24,7 @@
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.TRANSIT_CHANGE;
+import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -300,6 +301,10 @@
finishTransaction);
}
+ if (isCurrentPipActivityClosed(info)) {
+ mPipBoundsState.setLastPipComponentName(null /* componentName */);
+ }
+
return false;
}
@@ -322,6 +327,21 @@
return true;
}
+ private boolean isCurrentPipActivityClosed(TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ boolean isTaskChange = change.getTaskInfo() != null;
+ boolean hasComponentNameOfPip = change.getActivityComponent() != null
+ && change.getActivityComponent().equals(
+ mPipBoundsState.getLastPipComponentName());
+ if (!isTaskChange && change.getMode() == TRANSIT_CLOSE && hasComponentNameOfPip) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 6eefdcf..a7c47f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -53,8 +53,9 @@
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Executor;
/**
* Responsible supplying PiP Transitions.
@@ -66,7 +67,7 @@
protected final ShellTaskOrganizer mShellTaskOrganizer;
protected final PipMenuController mPipMenuController;
protected final Transitions mTransitions;
- private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
+ private final Map<PipTransitionCallback, Executor> mPipTransitionCallbacks = new HashMap<>();
protected PipTaskOrganizer mPipOrganizer;
protected DefaultMixedHandler mMixedHandler;
@@ -181,16 +182,18 @@
/**
* Registers {@link PipTransitionCallback} to receive transition callbacks.
*/
- public void registerPipTransitionCallback(PipTransitionCallback callback) {
- mPipTransitionCallbacks.add(callback);
+ public void registerPipTransitionCallback(
+ @NonNull PipTransitionCallback callback, @NonNull Executor executor) {
+ mPipTransitionCallbacks.put(callback, executor);
}
protected void sendOnPipTransitionStarted(
@PipAnimationController.TransitionDirection int direction) {
final Rect pipBounds = mPipBoundsState.getBounds();
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionStarted(direction, pipBounds);
+ for (Map.Entry<PipTransitionCallback, Executor> entry
+ : mPipTransitionCallbacks.entrySet()) {
+ entry.getValue().execute(
+ () -> entry.getKey().onPipTransitionStarted(direction, pipBounds));
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -207,9 +210,10 @@
protected void sendOnPipTransitionFinished(
@PipAnimationController.TransitionDirection int direction) {
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionFinished(direction);
+ for (Map.Entry<PipTransitionCallback, Executor> entry
+ : mPipTransitionCallbacks.entrySet()) {
+ entry.getValue().execute(
+ () -> entry.getKey().onPipTransitionFinished(direction));
}
if (isInPipDirection(direction) && Flags.enablePipUiStateCallbackOnEntering()) {
try {
@@ -226,9 +230,10 @@
protected void sendOnPipTransitionCancelled(
@PipAnimationController.TransitionDirection int direction) {
- for (int i = mPipTransitionCallbacks.size() - 1; i >= 0; i--) {
- final PipTransitionCallback callback = mPipTransitionCallbacks.get(i);
- callback.onPipTransitionCanceled(direction);
+ for (Map.Entry<PipTransitionCallback, Executor> entry
+ : mPipTransitionCallbacks.entrySet()) {
+ entry.getValue().execute(
+ () -> entry.getKey().onPipTransitionCanceled(direction));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 8c4bf76..448d4f5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -106,6 +106,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/**
@@ -367,15 +368,6 @@
false /* fromRotation */, fromImeAdjustment, false /* fromShelfAdjustment */,
null /* windowContainerTransaction */);
}
-
- @Override
- public void onActivityHidden(ComponentName componentName) {
- if (componentName.equals(mPipBoundsState.getLastPipComponentName())) {
- // The activity was removed, we don't want to restore to the reentry state
- // saved for this component anymore.
- mPipBoundsState.setLastPipComponentName(null);
- }
- }
}
/**
@@ -487,7 +479,7 @@
mShellCommandHandler.addDumpCallback(this::dump, this);
mPipInputConsumer = new PipInputConsumer(WindowManagerGlobal.getWindowManagerService(),
INPUT_CONSUMER_PIP, mMainExecutor);
- mPipTransitionController.registerPipTransitionCallback(this);
+ mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
mPipTaskOrganizer.registerOnDisplayIdChangeCallback((int displayId) -> {
mPipDisplayLayoutState.setDisplayId(displayId);
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
@@ -1229,8 +1221,11 @@
}
@Override
- public PipTransitionController getPipTransitionController() {
- return mPipTransitionController;
+ public void registerPipTransitionCallback(
+ PipTransitionController.PipTransitionCallback callback,
+ Executor executor) {
+ mMainExecutor.execute(() -> mPipTransitionController.registerPipTransitionCallback(
+ callback, executor));
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index ef46843..f5bd006 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -38,6 +38,7 @@
import com.android.wm.shell.R;
import com.android.wm.shell.animation.FloatProperties;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import com.android.wm.shell.common.pip.PipAppOpsListener;
import com.android.wm.shell.common.pip.PipBoundsState;
@@ -47,6 +48,7 @@
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
@@ -171,7 +173,9 @@
public void onPipTransitionCanceled(int direction) {}
};
- public PipMotionHelper(Context context, @NonNull PipBoundsState pipBoundsState,
+ public PipMotionHelper(Context context,
+ @ShellMainThread ShellExecutor mainExecutor,
+ @NonNull PipBoundsState pipBoundsState,
PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController,
PipSnapAlgorithm snapAlgorithm, PipTransitionController pipTransitionController,
FloatingContentCoordinator floatingContentCoordinator,
@@ -183,7 +187,7 @@
mSnapAlgorithm = snapAlgorithm;
mFloatingContentCoordinator = floatingContentCoordinator;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
- pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback);
+ pipTransitionController.registerPipTransitionCallback(mPipTransitionCallback, mainExecutor);
mResizePipUpdateListener = (target, values) -> {
if (mPipBoundsState.getMotionBoundsState().isInMotion()) {
mPipTaskOrganizer.scheduleUserResizePip(getBounds(),
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index 3d28646..b6a7c56 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -257,7 +257,7 @@
}
private void onInit() {
- mPipTransitionController.registerPipTransitionCallback(this);
+ mPipTransitionController.registerPipTransitionCallback(this, mMainExecutor);
reloadResources();
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/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
index e8b01b5..7a42236 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -184,7 +184,8 @@
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
TransitionInfo.Change change = info.getChanges().get(i);
- if (change == pipChange || !isOpeningMode(change.getMode())) {
+ if (change == pipChange || !isOpeningMode(change.getMode()) ||
+ change.getTaskInfo() == null) {
// Ignore the change/task that's going into Pip or not opening
continue;
}
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/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index e1009a0..180e4f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -26,7 +26,6 @@
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
import static android.view.MotionEvent.ACTION_HOVER_EXIT;
-import static android.view.MotionEvent.ACTION_HOVER_MOVE;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.WindowInsets.Type.statusBars;
@@ -103,6 +102,7 @@
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
+import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
import java.io.PrintWriter;
import java.util.Objects;
@@ -383,10 +383,32 @@
mWindowDecorByTaskId.remove(taskInfo.taskId);
}
+ private void onMaximizeOrRestore(int taskId, String tag) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ InteractionJankMonitorUtils.beginTracing(
+ Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, mContext, decoration.mTaskSurface, tag);
+ mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
+ }
+
+ private void onSnapResize(int taskId, boolean left) {
+ final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskId);
+ if (decoration == null) {
+ return;
+ }
+ mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo,
+ left ? SnapPosition.LEFT : SnapPosition.RIGHT);
+ decoration.closeHandleMenu();
+ decoration.closeMaximizeMenu();
+ }
+
private class DesktopModeTouchEventListener extends GestureDetector.SimpleOnGestureListener
implements View.OnClickListener, View.OnTouchListener, View.OnLongClickListener,
View.OnGenericMotionListener, DragDetector.MotionEventHandler {
- private static final int CLOSE_MAXIMIZE_MENU_DELAY_MS = 150;
private final int mTaskId;
private final WindowContainerToken mTaskToken;
@@ -405,7 +427,6 @@
private boolean mTouchscreenInUse;
private boolean mHasLongClicked;
private int mDragPointerId = -1;
- private final Runnable mCloseMaximizeWindowRunnable;
private DesktopModeTouchEventListener(
RunningTaskInfo taskInfo,
@@ -416,11 +437,6 @@
mDragDetector = new DragDetector(this);
mGestureDetector = new GestureDetector(mContext, this);
mDisplayId = taskInfo.displayId;
- mCloseMaximizeWindowRunnable = () -> {
- final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- if (decoration == null) return;
- decoration.closeMaximizeMenu();
- };
}
@Override
@@ -472,31 +488,12 @@
} else if (id == R.id.collapse_menu_button) {
decoration.closeHandleMenu();
} else if (id == R.id.maximize_window) {
- InteractionJankMonitorUtils.beginTracing(
- Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, /* view= */ v,
- /* tag= */ "caption_bar_button");
- final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- decoration.closeHandleMenu();
- decoration.closeMaximizeMenu();
- mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
- } else if (id == R.id.maximize_menu_maximize_button) {
- InteractionJankMonitorUtils.beginTracing(
- Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, /* view= */ v,
- /* tag= */ "maximize_menu_option");
- final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.toggleDesktopTaskSize(taskInfo);
- decoration.closeHandleMenu();
- decoration.closeMaximizeMenu();
- } else if (id == R.id.maximize_menu_snap_left_button) {
- final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.LEFT);
- decoration.closeHandleMenu();
- decoration.closeMaximizeMenu();
- } else if (id == R.id.maximize_menu_snap_right_button) {
- final RunningTaskInfo taskInfo = decoration.mTaskInfo;
- mDesktopTasksController.snapToHalfScreen(taskInfo, SnapPosition.RIGHT);
- decoration.closeHandleMenu();
- decoration.closeMaximizeMenu();
+ // TODO(b/346441962): move click detection logic into the decor's
+ // {@link AppHeaderViewHolder}. Let it encapsulate the that and have it report
+ // back to the decoration using
+ // {@link DesktopModeWindowDecoration#setOnMaximizeOrRestoreClickListener}, which
+ // should shared with the maximize menu's maximize/restore actions.
+ onMaximizeOrRestore(decoration.mTaskInfo.taskId, "caption_bar_button");
}
}
@@ -578,40 +575,26 @@
return false;
}
+ /**
+ * TODO(b/346441962): move this hover detection logic into the decor's
+ * {@link AppHeaderViewHolder}.
+ */
@Override
public boolean onGenericMotion(View v, MotionEvent ev) {
final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
final int id = v.getId();
- if (ev.getAction() == ACTION_HOVER_ENTER) {
- if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) {
- decoration.onMaximizeWindowHoverEnter();
- } else if (id == R.id.maximize_window
- || MaximizeMenu.Companion.isMaximizeMenuView(id)) {
- // Re-hovering over any of the maximize menu views should keep the menu open by
- // cancelling any attempts to close the menu.
- mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable);
- if (id != R.id.maximize_window) {
- decoration.onMaximizeMenuHoverEnter(id, ev);
- }
+ if (ev.getAction() == ACTION_HOVER_ENTER && id == R.id.maximize_window) {
+ decoration.setAppHeaderMaximizeButtonHovered(true);
+ if (!decoration.isMaximizeMenuActive()) {
+ decoration.onMaximizeButtonHoverEnter();
}
return true;
- } else if (ev.getAction() == ACTION_HOVER_MOVE
- && MaximizeMenu.Companion.isMaximizeMenuView(id)) {
- decoration.onMaximizeMenuHoverMove(id, ev);
- mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable);
- } else if (ev.getAction() == ACTION_HOVER_EXIT) {
- if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) {
- decoration.onMaximizeWindowHoverExit();
- } else if (id == R.id.maximize_window
- || MaximizeMenu.Companion.isMaximizeMenuView(id)) {
- // Close menu if not hovering over maximize menu or maximize button after a
- // delay to give user a chance to re-enter view or to move from one maximize
- // menu view to another.
- mMainHandler.postDelayed(mCloseMaximizeWindowRunnable,
- CLOSE_MAXIMIZE_MENU_DELAY_MS);
- if (id != R.id.maximize_window) {
- decoration.onMaximizeMenuHoverExit(id, ev);
- }
+ }
+ if (ev.getAction() == ACTION_HOVER_EXIT && id == R.id.maximize_window) {
+ decoration.setAppHeaderMaximizeButtonHovered(false);
+ decoration.onMaximizeHoverStateChanged();
+ if (!decoration.isMaximizeMenuActive()) {
+ decoration.onMaximizeButtonHoverExit();
}
return true;
}
@@ -719,11 +702,7 @@
&& action != MotionEvent.ACTION_CANCEL)) {
return false;
}
- final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
- InteractionJankMonitorUtils.beginTracing(
- Cuj.CUJ_DESKTOP_MODE_MAXIMIZE_WINDOW, mContext,
- /* surface= */ decoration.mTaskSurface, /* tag= */ "double_tap");
- mDesktopTasksController.toggleDesktopTaskSize(decoration.mTaskInfo);
+ onMaximizeOrRestore(mTaskId, "double_tap");
return true;
}
}
@@ -1105,7 +1084,13 @@
final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
-
+ windowDecoration.setOnMaximizeOrRestoreClickListener(this::onMaximizeOrRestore);
+ windowDecoration.setOnLeftSnapClickListener((taskId, tag) -> {
+ onSnapResize(taskId, true /* isLeft */);
+ });
+ windowDecoration.setOnRightSnapClickListener((taskId, tag) -> {
+ onSnapResize(taskId, false /* isLeft */);
+ });
windowDecoration.setCaptionListeners(
touchEventListener, touchEventListener, touchEventListener, touchEventListener);
windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 4d597ca..f53c21d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -69,6 +69,7 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
import com.android.wm.shell.windowdecor.extension.TaskInfoKt;
import com.android.wm.shell.windowdecor.viewholder.AppHandleViewHolder;
import com.android.wm.shell.windowdecor.viewholder.AppHeaderViewHolder;
@@ -87,6 +88,9 @@
public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
private static final String TAG = "DesktopModeWindowDecoration";
+ @VisibleForTesting
+ static final long CLOSE_MAXIMIZE_MENU_DELAY_MS = 150L;
+
private final Handler mHandler;
private final Choreographer mChoreographer;
private final SyncTransactionQueue mSyncQueue;
@@ -96,6 +100,9 @@
private View.OnTouchListener mOnCaptionTouchListener;
private View.OnLongClickListener mOnCaptionLongClickListener;
private View.OnGenericMotionListener mOnCaptionGenericMotionListener;
+ private OnTaskActionClickListener mOnMaximizeOrRestoreClickListener;
+ private OnTaskActionClickListener mOnLeftSnapClickListener;
+ private OnTaskActionClickListener mOnRightSnapClickListener;
private DragPositioningCallback mDragPositioningCallback;
private DragResizeInputListener mDragResizeListener;
private DragDetector mDragDetector;
@@ -120,6 +127,16 @@
private ExclusionRegionListener mExclusionRegionListener;
private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
+ private final MaximizeMenuFactory mMaximizeMenuFactory;
+
+ // Hover state for the maximize menu and button. The menu will remain open as long as either of
+ // these is true. See {@link #onMaximizeHoverStateChanged()}.
+ private boolean mIsAppHeaderMaximizeButtonHovered = false;
+ private boolean mIsMaximizeMenuHovered = false;
+ // Used to schedule the closing of the maximize menu when neither of the button or menu are
+ // being hovered. There's a small delay after stopping the hover, to allow a quick reentry
+ // to cancel the close.
+ private final Runnable mCloseMaximizeWindowRunnable = this::closeMaximizeMenu;
DesktopModeWindowDecoration(
Context context,
@@ -135,7 +152,8 @@
handler, choreographer, syncQueue, rootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, SurfaceControl.Transaction::new,
WindowContainerTransaction::new, SurfaceControl::new,
- new SurfaceControlViewHostFactory() {});
+ new SurfaceControlViewHostFactory() {},
+ DefaultMaximizeMenuFactory.INSTANCE);
}
DesktopModeWindowDecoration(
@@ -152,7 +170,8 @@
Supplier<SurfaceControl.Transaction> surfaceControlTransactionSupplier,
Supplier<WindowContainerTransaction> windowContainerTransactionSupplier,
Supplier<SurfaceControl> surfaceControlSupplier,
- SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
+ SurfaceControlViewHostFactory surfaceControlViewHostFactory,
+ MaximizeMenuFactory maximizeMenuFactory) {
super(context, displayController, taskOrganizer, taskInfo, taskSurface,
surfaceControlBuilderSupplier, surfaceControlTransactionSupplier,
windowContainerTransactionSupplier, surfaceControlSupplier,
@@ -161,6 +180,31 @@
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
+ mMaximizeMenuFactory = maximizeMenuFactory;
+ }
+
+ /**
+ * Register a listener to be called back when one of the tasks' maximize/restore action is
+ * triggered.
+ * TODO(b/346441962): hook this up to double-tap and the header's maximize button, instead of
+ * having the ViewModel deal with parsing motion events.
+ */
+ void setOnMaximizeOrRestoreClickListener(OnTaskActionClickListener listener) {
+ mOnMaximizeOrRestoreClickListener = listener;
+ }
+
+ /**
+ * Register a listener to be called back when one of the tasks snap-left action is triggered.
+ */
+ void setOnLeftSnapClickListener(OnTaskActionClickListener listener) {
+ mOnLeftSnapClickListener = listener;
+ }
+
+ /**
+ * Register a listener to be called back when one of the tasks' snap-right action is triggered.
+ */
+ void setOnRightSnapClickListener(OnTaskActionClickListener listener) {
+ mOnRightSnapClickListener = listener;
}
void setCaptionListeners(
@@ -714,11 +758,41 @@
* Create and display maximize menu window
*/
void createMaximizeMenu() {
- mMaximizeMenu = new MaximizeMenu(mSyncQueue, mRootTaskDisplayAreaOrganizer,
- mDisplayController, mTaskInfo, mOnCaptionButtonClickListener,
- mOnCaptionGenericMotionListener, mOnCaptionTouchListener, mContext,
+ mMaximizeMenu = mMaximizeMenuFactory.create(mSyncQueue, mRootTaskDisplayAreaOrganizer,
+ mDisplayController, mTaskInfo, mContext,
calculateMaximizeMenuPosition(), mSurfaceControlTransactionSupplier);
- mMaximizeMenu.show();
+ mMaximizeMenu.show(
+ mOnMaximizeOrRestoreClickListener,
+ mOnLeftSnapClickListener,
+ mOnRightSnapClickListener,
+ hovered -> {
+ mIsMaximizeMenuHovered = hovered;
+ onMaximizeHoverStateChanged();
+ return null;
+ }
+ );
+ }
+
+ /** Set whether the app header's maximize button is hovered. */
+ void setAppHeaderMaximizeButtonHovered(boolean hovered) {
+ mIsAppHeaderMaximizeButtonHovered = hovered;
+ onMaximizeHoverStateChanged();
+ }
+
+ /**
+ * Called when either one of the maximize button in the app header or the maximize menu has
+ * changed its hover state.
+ */
+ void onMaximizeHoverStateChanged() {
+ if (!mIsMaximizeMenuHovered && !mIsAppHeaderMaximizeButtonHovered) {
+ // Neither is hovered, close the menu.
+ if (isMaximizeMenuActive()) {
+ mHandler.postDelayed(mCloseMaximizeWindowRunnable, CLOSE_MAXIMIZE_MENU_DELAY_MS);
+ }
+ return;
+ }
+ // At least one of the two is hovered, cancel the close if needed.
+ mHandler.removeCallbacks(mCloseMaximizeWindowRunnable);
}
/**
@@ -992,34 +1066,22 @@
.setAnimatingTaskResize(animatingTaskResize);
}
- /** Called when there is a {@Link ACTION_HOVER_EXIT} on the maximize window button. */
- void onMaximizeWindowHoverExit() {
+ /**
+ * Called when there is a {@link MotionEvent#ACTION_HOVER_EXIT} on the maximize window button.
+ */
+ void onMaximizeButtonHoverExit() {
((AppHeaderViewHolder) mWindowDecorViewHolder)
.onMaximizeWindowHoverExit();
}
- /** Called when there is a {@Link ACTION_HOVER_ENTER} on the maximize window button. */
- void onMaximizeWindowHoverEnter() {
+ /**
+ * Called when there is a {@link MotionEvent#ACTION_HOVER_ENTER} on the maximize window button.
+ */
+ void onMaximizeButtonHoverEnter() {
((AppHeaderViewHolder) mWindowDecorViewHolder)
.onMaximizeWindowHoverEnter();
}
- /** Called when there is a {@Link ACTION_HOVER_ENTER} on a view in the maximize menu. */
- void onMaximizeMenuHoverEnter(int id, MotionEvent ev) {
- mMaximizeMenu.onMaximizeMenuHoverEnter(id, ev);
- }
-
- /** Called when there is a {@Link ACTION_HOVER_MOVE} on a view in the maximize menu. */
- void onMaximizeMenuHoverMove(int id, MotionEvent ev) {
- mMaximizeMenu.onMaximizeMenuHoverMove(id, ev);
- }
-
- /** Called when there is a {@Link ACTION_HOVER_EXIT} on a view in the maximize menu. */
- void onMaximizeMenuHoverExit(int id, MotionEvent ev) {
- mMaximizeMenu.onMaximizeMenuHoverExit(id, ev);
- }
-
-
@Override
public String toString() {
return "{"
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 0470367..5f9f8d6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -20,7 +20,6 @@
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.ColorInt
-import android.annotation.IdRes
import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.content.res.ColorStateList
@@ -28,6 +27,7 @@
import android.graphics.Paint
import android.graphics.PixelFormat
import android.graphics.PointF
+import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.LayerDrawable
@@ -37,16 +37,17 @@
import android.util.StateSet
import android.view.LayoutInflater
import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_HOVER_ENTER
+import android.view.MotionEvent.ACTION_HOVER_EXIT
+import android.view.MotionEvent.ACTION_HOVER_MOVE
import android.view.SurfaceControl
import android.view.SurfaceControl.Transaction
import android.view.SurfaceControlViewHost
import android.view.View
-import android.view.View.OnClickListener
-import android.view.View.OnGenericMotionListener
-import android.view.View.OnTouchListener
import android.view.View.SCALE_Y
import android.view.View.TRANSLATION_Y
import android.view.View.TRANSLATION_Z
+import android.view.ViewGroup
import android.view.WindowManager
import android.view.WindowlessWindowManager
import android.widget.Button
@@ -64,10 +65,10 @@
import com.android.wm.shell.windowdecor.common.DecorThemeUtil
import com.android.wm.shell.windowdecor.common.OPACITY_12
import com.android.wm.shell.windowdecor.common.OPACITY_40
+import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
import com.android.wm.shell.windowdecor.common.withAlpha
import java.util.function.Supplier
-
/**
* Menu that appears when user long clicks the maximize button. Gives the user the option to
* maximize the task or snap the task to the right or left half of the screen.
@@ -77,9 +78,6 @@
private val rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
private val displayController: DisplayController,
private val taskInfo: RunningTaskInfo,
- private val onClickListener: OnClickListener,
- private val onGenericMotionListener: OnGenericMotionListener,
- private val onTouchListener: OnTouchListener,
private val decorWindowContext: Context,
private val menuPosition: PointF,
private val transactionSupplier: Supplier<Transaction> = Supplier { Transaction() }
@@ -102,9 +100,19 @@
}
/** Creates and shows the maximize window. */
- fun show() {
+ fun show(
+ onMaximizeClickListener: OnTaskActionClickListener,
+ onLeftSnapClickListener: OnTaskActionClickListener,
+ onRightSnapClickListener: OnTaskActionClickListener,
+ onHoverListener: (Boolean) -> Unit
+ ) {
if (maximizeMenu != null) return
- createMaximizeMenu()
+ createMaximizeMenu(
+ onMaximizeClickListener = onMaximizeClickListener,
+ onLeftSnapClickListener = onLeftSnapClickListener,
+ onRightSnapClickListener = onRightSnapClickListener,
+ onHoverListener = onHoverListener
+ )
maximizeMenuView?.animateOpenMenu()
}
@@ -117,7 +125,12 @@
}
/** Create a maximize menu that is attached to the display area. */
- private fun createMaximizeMenu() {
+ private fun createMaximizeMenu(
+ onMaximizeClickListener: OnTaskActionClickListener,
+ onLeftSnapClickListener: OnTaskActionClickListener,
+ onRightSnapClickListener: OnTaskActionClickListener,
+ onHoverListener: (Boolean) -> Unit
+ ) {
val t = transactionSupplier.get()
val builder = SurfaceControl.Builder()
rootTdaOrganizer.attachToDisplayArea(taskInfo.displayId, builder)
@@ -146,11 +159,19 @@
context = decorWindowContext,
menuHeight = menuHeight,
menuPadding = menuPadding,
- onClickListener = onClickListener,
- onTouchListener = onTouchListener,
- onGenericMotionListener = onGenericMotionListener,
).also { menuView ->
+ val taskId = taskInfo.taskId
menuView.bind(taskInfo)
+ menuView.onMaximizeClickListener = {
+ onMaximizeClickListener.onClick(taskId, "maximize_menu_option")
+ }
+ menuView.onLeftSnapClickListener = {
+ onLeftSnapClickListener.onClick(taskId, "left_snap_option")
+ }
+ menuView.onRightSnapClickListener = {
+ onRightSnapClickListener.onClick(taskId, "right_snap_option")
+ }
+ menuView.onMenuHoverListener = onHoverListener
viewHost.setView(menuView.rootView, lp)
}
@@ -198,56 +219,6 @@
}
/**
- * Called when a [MotionEvent.ACTION_HOVER_ENTER] is triggered on any of the menu's views.
- *
- * TODO(b/346440693): this is only needed for the left/right snap options that don't support
- * selector states to manage its hover state. Look into whether that can be added to avoid
- * manually tracking hover enter/exit motion events. Also because those button colors/states
- * aren't updating correctly for pressed, focused and selected states.
- * See also [onMaximizeMenuHoverMove] and [onMaximizeMenuHoverExit].
- */
- fun onMaximizeMenuHoverEnter(viewId: Int, ev: MotionEvent) {
- setSnapButtonsColorOnHover(viewId, ev)
- }
-
- /** Called when a [MotionEvent.ACTION_HOVER_MOVE] is triggered on any of the menu's views. */
- fun onMaximizeMenuHoverMove(viewId: Int, ev: MotionEvent) {
- setSnapButtonsColorOnHover(viewId, ev)
- }
-
- /** Called when a [MotionEvent.ACTION_HOVER_EXIT] is triggered on any of the menu's views. */
- fun onMaximizeMenuHoverExit(id: Int, ev: MotionEvent) {
- val snapOptionsWidth = maximizeMenuView?.snapOptionsWidth ?: return
- val snapOptionsHeight = maximizeMenuView?.snapOptionsHeight ?: return
- val inSnapMenuBounds = ev.x >= 0 && ev.x <= snapOptionsWidth &&
- ev.y >= 0 && ev.y <= snapOptionsHeight
-
- if (id == R.id.maximize_menu_snap_menu_layout && !inSnapMenuBounds) {
- // After exiting the snap menu layout area, checks to see that user is not still
- // hovering within the snap menu layout bounds which would indicate that the user is
- // hovering over a snap button within the snap menu layout rather than having exited.
- maximizeMenuView?.updateSplitSnapSelection(MaximizeMenuView.SnapToHalfSelection.NONE)
- }
- }
-
- private fun setSnapButtonsColorOnHover(viewId: Int, ev: MotionEvent) {
- val snapOptionsWidth = maximizeMenuView?.snapOptionsWidth ?: return
- val snapMenuCenter = snapOptionsWidth / 2
- when {
- viewId == R.id.maximize_menu_snap_left_button ||
- (viewId == R.id.maximize_menu_snap_menu_layout && ev.x <= snapMenuCenter) -> {
- maximizeMenuView
- ?.updateSplitSnapSelection(MaximizeMenuView.SnapToHalfSelection.LEFT)
- }
- viewId == R.id.maximize_menu_snap_right_button ||
- (viewId == R.id.maximize_menu_snap_menu_layout && ev.x > snapMenuCenter) -> {
- maximizeMenuView
- ?.updateSplitSnapSelection(MaximizeMenuView.SnapToHalfSelection.RIGHT)
- }
- }
- }
-
- /**
* The view within the Maximize Menu, presents maximize, restore and snap-to-side options for
* resizing a Task.
*/
@@ -255,12 +226,11 @@
context: Context,
private val menuHeight: Int,
private val menuPadding: Int,
- onClickListener: OnClickListener,
- onTouchListener: OnTouchListener,
- onGenericMotionListener: OnGenericMotionListener,
) {
- val rootView: View = LayoutInflater.from(context)
- .inflate(R.layout.desktop_mode_window_decor_maximize_menu, null /* root */)
+ val rootView = LayoutInflater.from(context)
+ .inflate(R.layout.desktop_mode_window_decor_maximize_menu, null /* root */) as ViewGroup
+ private val container = requireViewById(R.id.container)
+ private val overlay = requireViewById(R.id.maximize_menu_overlay)
private val maximizeText =
requireViewById(R.id.maximize_menu_maximize_window_text) as TextView
private val maximizeButton =
@@ -285,30 +255,63 @@
private val fillRadius = context.resources
.getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_fill_radius)
+ private val hoverTempRect = Rect()
private val openMenuAnimatorSet = AnimatorSet()
private lateinit var taskInfo: RunningTaskInfo
private lateinit var style: MenuStyle
- /** The width of the snap menu option view, including both left and right snaps. */
- val snapOptionsWidth: Int
- get() = snapButtonsLayout.width
- /** The height of the snap menu option view, including both left and right snaps .*/
- val snapOptionsHeight: Int
- get() = snapButtonsLayout.height
+ /** Invoked when the maximize or restore option is clicked. */
+ var onMaximizeClickListener: (() -> Unit)? = null
+ /** Invoked when the left snap option is clicked. */
+ var onLeftSnapClickListener: (() -> Unit)? = null
+ /** Invoked when the right snap option is clicked. */
+ var onRightSnapClickListener: (() -> Unit)? = null
+ /** Invoked whenever the hover state of the menu changes. */
+ var onMenuHoverListener: ((Boolean) -> Unit)? = null
init {
- // TODO(b/346441962): encapsulate menu hover enter/exit logic inside this class and
- // expose only what is actually relevant to outside classes so that specific checks
- // against resource IDs aren't needed outside this class.
- rootView.setOnGenericMotionListener(onGenericMotionListener)
- rootView.setOnTouchListener(onTouchListener)
- maximizeButton.setOnClickListener(onClickListener)
- maximizeButton.setOnGenericMotionListener(onGenericMotionListener)
- snapRightButton.setOnClickListener(onClickListener)
- snapRightButton.setOnGenericMotionListener(onGenericMotionListener)
- snapLeftButton.setOnClickListener(onClickListener)
- snapLeftButton.setOnGenericMotionListener(onGenericMotionListener)
- snapButtonsLayout.setOnGenericMotionListener(onGenericMotionListener)
+ overlay.setOnHoverListener { _, event ->
+ // The overlay covers the entire menu, so it's a convenient way to monitor whether
+ // the menu is hovered as a whole or not.
+ when (event.action) {
+ ACTION_HOVER_ENTER -> onMenuHoverListener?.invoke(true)
+ ACTION_HOVER_EXIT -> onMenuHoverListener?.invoke(false)
+ }
+
+ // Also check if the hover falls within the snap options layout, to manually
+ // set the left/right state based on the event's position.
+ // TODO(b/346440693): this manual hover tracking is needed for left/right snap
+ // because its view/background(s) don't support selector states. Look into whether
+ // that can be added to avoid manual tracking. Also because these button
+ // colors/state logic is only being applied on hover events, but there's pressed,
+ // focused and selected states that should be responsive too.
+ val snapLayoutBoundsRelToOverlay = hoverTempRect.also { rect ->
+ snapButtonsLayout.getDrawingRect(rect)
+ rootView.offsetDescendantRectToMyCoords(snapButtonsLayout, rect)
+ }
+ if (event.action == ACTION_HOVER_ENTER || event.action == ACTION_HOVER_MOVE) {
+ if (snapLayoutBoundsRelToOverlay.contains(event.x.toInt(), event.y.toInt())) {
+ // Hover is inside the snap layout, anything left of center is the left
+ // snap, and anything right of center is right snap.
+ val layoutCenter = snapLayoutBoundsRelToOverlay.centerX()
+ if (event.x < layoutCenter) {
+ updateSplitSnapSelection(SnapToHalfSelection.LEFT)
+ } else {
+ updateSplitSnapSelection(SnapToHalfSelection.RIGHT)
+ }
+ } else {
+ // Any other hover is outside the snap layout, so neither is selected.
+ updateSplitSnapSelection(SnapToHalfSelection.NONE)
+ }
+ }
+
+ // Don't consume the event to allow child views to receive the event too.
+ return@setOnHoverListener false
+ }
+
+ maximizeButton.setOnClickListener { onMaximizeClickListener?.invoke() }
+ snapRightButton.setOnClickListener { onRightSnapClickListener?.invoke() }
+ snapLeftButton.setOnClickListener { onLeftSnapClickListener?.invoke() }
// To prevent aliasing.
maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null)
@@ -351,7 +354,7 @@
val value = animatedValue as Float
val topPadding = menuPadding -
((1 - value) * menuHeight).toInt()
- rootView.setPadding(menuPadding, topPadding,
+ container.setPadding(menuPadding, topPadding,
menuPadding, menuPadding)
}
},
@@ -410,7 +413,7 @@
}
/** Update the view state to a new snap to half selection. */
- fun updateSplitSnapSelection(selection: SnapToHalfSelection) {
+ private fun updateSplitSnapSelection(selection: SnapToHalfSelection) {
when (selection) {
SnapToHalfSelection.NONE -> deactivateSnapOptions()
SnapToHalfSelection.LEFT -> activateSnapOption(activateLeft = true)
@@ -638,13 +641,41 @@
private const val ELEVATION_ANIMATION_DURATION_MS = 50L
private const val CONTROLS_ALPHA_ANIMATION_DELAY_MS = 33L
private const val MENU_Z_TRANSLATION = 1f
- fun isMaximizeMenuView(@IdRes viewId: Int): Boolean {
- return viewId == R.id.maximize_menu ||
- viewId == R.id.maximize_menu_maximize_button ||
- viewId == R.id.maximize_menu_snap_left_button ||
- viewId == R.id.maximize_menu_snap_right_button ||
- viewId == R.id.maximize_menu_snap_menu_layout ||
- viewId == R.id.maximize_menu_snap_menu_layout
- }
+ }
+}
+
+/** A factory interface to create a [MaximizeMenu]. */
+interface MaximizeMenuFactory {
+ fun create(
+ syncQueue: SyncTransactionQueue,
+ rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
+ displayController: DisplayController,
+ taskInfo: RunningTaskInfo,
+ decorWindowContext: Context,
+ menuPosition: PointF,
+ transactionSupplier: Supplier<Transaction>
+ ): MaximizeMenu
+}
+
+/** A [MaximizeMenuFactory] implementation that creates a [MaximizeMenu]. */
+object DefaultMaximizeMenuFactory : MaximizeMenuFactory {
+ override fun create(
+ syncQueue: SyncTransactionQueue,
+ rootTdaOrganizer: RootTaskDisplayAreaOrganizer,
+ displayController: DisplayController,
+ taskInfo: RunningTaskInfo,
+ decorWindowContext: Context,
+ menuPosition: PointF,
+ transactionSupplier: Supplier<Transaction>
+ ): MaximizeMenu {
+ return MaximizeMenu(
+ syncQueue,
+ rootTdaOrganizer,
+ displayController,
+ taskInfo,
+ decorWindowContext,
+ menuPosition,
+ transactionSupplier
+ )
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt
new file mode 100644
index 0000000..14b9e7f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/common/OnTaskActionClickListener.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.windowdecor.common
+
+/** A callback to be invoked when a Task's window decor element is clicked. */
+fun interface OnTaskActionClickListener {
+ /**
+ * Called when a task's decor element has been clicked.
+ *
+ * @param taskId the id of the task.
+ * @param tag a readable identifier for the element.
+ */
+ fun onClick(taskId: Int, tag: String)
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index f9b4108..8303317 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -687,6 +687,25 @@
verify(mRecentTasksController).onTaskRunningInfoChanged(task2);
}
+ @Test
+ public void testTaskVanishedCallback() {
+ RunningTaskInfo task1 = createTaskInfo(/* taskId= */ 1, WINDOWING_MODE_FULLSCREEN);
+ mOrganizer.onTaskAppeared(task1, /* leash= */ null);
+
+ RunningTaskInfo[] vanishedTasks = new RunningTaskInfo[1];
+ ShellTaskOrganizer.TaskVanishedListener listener =
+ new ShellTaskOrganizer.TaskVanishedListener() {
+ @Override
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
+ vanishedTasks[0] = taskInfo;
+ }
+ };
+ mOrganizer.addTaskVanishedListener(listener);
+ mOrganizer.onTaskVanished(task1);
+
+ assertEquals(vanishedTasks[0], task1);
+ }
+
private static RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
RunningTaskInfo taskInfo = new RunningTaskInfo();
taskInfo.taskId = taskId;
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/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index a64ebd3..8401264 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -76,6 +76,8 @@
@Mock
private ShellCommandHandler mShellCommandHandler;
@Mock
+ private ShellTaskOrganizer mShellTaskOrganizer;
+ @Mock
private DisplayController mDisplayController;
@Mock
private UiEventLogger mUiEventLogger;
@@ -96,8 +98,8 @@
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
mController = new DragAndDropController(mContext, mShellInit, mShellController,
- mShellCommandHandler, mDisplayController, mUiEventLogger, mIconProvider,
- mGlobalDragListener, mTransitions, mMainExecutor);
+ mShellCommandHandler, mShellTaskOrganizer, mDisplayController, mUiEventLogger,
+ mIconProvider, mGlobalDragListener, mTransitions, mMainExecutor);
mController.onInit();
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 6e72e8d..582fb91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -65,8 +65,6 @@
import android.graphics.Insets;
import android.os.RemoteException;
import android.view.DisplayInfo;
-import android.view.DragEvent;
-import android.view.View;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
@@ -76,7 +74,6 @@
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.startingsurface.TaskSnapshotWindow;
import org.junit.After;
import org.junit.Before;
@@ -106,6 +103,8 @@
// Both the split-screen and start interface.
@Mock
private SplitScreenController mSplitScreenStarter;
+ @Mock
+ private DragAndDropPolicy.Starter mFullscreenStarter;
@Mock
private InstanceId mLoggerSessionId;
@@ -151,7 +150,7 @@
mPortraitDisplayLayout = new DisplayLayout(info2, res, false, false);
mInsets = Insets.of(0, 0, 0, 0);
- mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mSplitScreenStarter));
+ mPolicy = spy(new DragAndDropPolicy(mContext, mSplitScreenStarter, mFullscreenStarter));
mActivityClipData = createAppClipData(MIMETYPE_APPLICATION_ACTIVITY);
mLaunchableIntentPendingIntent = mock(PendingIntent.class);
when(mLaunchableIntentPendingIntent.getCreatorUserHandle())
@@ -285,13 +284,13 @@
setRunningTask(mHomeTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
mLandscapeDisplayLayout, data, 0 /* dragFlags */);
- dragSession.update();
+ dragSession.initialize();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_FULLSCREEN);
mPolicy.handleDrop(filterTargetByType(targets, TYPE_FULLSCREEN));
- verify(mSplitScreenStarter).startIntent(any(), anyInt(), any(),
+ verify(mFullscreenStarter).startIntent(any(), anyInt(), any(),
eq(SPLIT_POSITION_UNDEFINED), any());
}
@@ -300,7 +299,7 @@
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
mLandscapeDisplayLayout, data, 0 /* dragFlags */);
- dragSession.update();
+ dragSession.initialize();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_LEFT, TYPE_SPLIT_RIGHT);
@@ -320,7 +319,7 @@
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
mPortraitDisplayLayout, data, 0 /* dragFlags */);
- dragSession.update();
+ dragSession.initialize();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = assertExactTargetTypes(
mPolicy.getTargets(mInsets), TYPE_SPLIT_TOP, TYPE_SPLIT_BOTTOM);
@@ -340,7 +339,7 @@
setRunningTask(mFullscreenAppTask);
DragSession dragSession = new DragSession(mActivityTaskManager,
mLandscapeDisplayLayout, mActivityClipData, 0 /* dragFlags */);
- dragSession.update();
+ dragSession.initialize();
mPolicy.start(dragSession, mLoggerSessionId);
ArrayList<Target> targets = mPolicy.getTargets(mInsets);
for (Target t : targets) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index d38fc6c..75d2145 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -34,7 +34,6 @@
import static java.lang.Integer.MAX_VALUE;
-import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
@@ -183,7 +182,7 @@
@Test
public void instantiatePipController_registersPipTransitionCallback() {
- verify(mMockPipTransitionController).registerPipTransitionCallback(any());
+ verify(mMockPipTransitionController).registerPipTransitionCallback(any(), any());
}
@Test
@@ -235,27 +234,6 @@
}
@Test
- public void onActivityHidden_isLastPipComponentName_clearLastPipComponent() {
- final ComponentName component1 = new ComponentName(mContext, "component1");
- when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1);
-
- mPipController.mPinnedTaskListener.onActivityHidden(component1);
-
- verify(mMockPipBoundsState).setLastPipComponentName(null);
- }
-
- @Test
- public void onActivityHidden_isNotLastPipComponentName_lastPipComponentNotCleared() {
- final ComponentName component1 = new ComponentName(mContext, "component1");
- final ComponentName component2 = new ComponentName(mContext, "component2");
- when(mMockPipBoundsState.getLastPipComponentName()).thenReturn(component1);
-
- mPipController.mPinnedTaskListener.onActivityHidden(component2);
-
- verify(mMockPipBoundsState, never()).setLastPipComponentName(null);
- }
-
- @Test
public void saveReentryState_savesPipBoundsState() {
final Rect bounds = new Rect(0, 0, 10, 10);
when(mMockPipBoundsAlgorithm.getSnapFraction(bounds)).thenReturn(1.0f);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
index ace09a8..66f8c0b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java
@@ -114,8 +114,8 @@
final PipBoundsAlgorithm pipBoundsAlgorithm = new PipBoundsAlgorithm(mContext,
mPipBoundsState, pipSnapAlgorithm, pipKeepClearAlgorithm, mPipDisplayLayoutState,
mSizeSpecSource);
- final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
- mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
+ final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mMainExecutor,
+ mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
index 92762fa..6d18e36 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipTouchHandlerTest.java
@@ -116,8 +116,8 @@
mPipSnapAlgorithm = new PipSnapAlgorithm();
mPipBoundsAlgorithm = new PipBoundsAlgorithm(mContext, mPipBoundsState, mPipSnapAlgorithm,
new PipKeepClearAlgorithmInterface() {}, mPipDisplayLayoutState, mSizeSpecSource);
- PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mPipBoundsState,
- mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
+ PipMotionHelper pipMotionHelper = new PipMotionHelper(mContext, mMainExecutor,
+ mPipBoundsState, mPipTaskOrganizer, mPhonePipMenuController, mPipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator,
Optional.empty() /* pipPerfHintControllerOptional */);
mPipTouchHandler = new PipTouchHandler(mContext, mShellInit, mPhonePipMenuController,
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/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
index ca1e3f1..4c94c29 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.kt
@@ -67,6 +67,7 @@
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTasksController
+import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
import com.android.wm.shell.freeform.FreeformTaskTransitionStarter
import com.android.wm.shell.shared.DesktopModeStatus
import com.android.wm.shell.sysui.KeyguardChangeListener
@@ -75,6 +76,7 @@
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.windowdecor.DesktopModeWindowDecorViewModel.DesktopModeOnInsetsChangedListener
+import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener
import java.util.Optional
import java.util.function.Supplier
import org.junit.Assert.assertEquals
@@ -82,6 +84,7 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
@@ -518,6 +521,99 @@
}
}
+ @Test
+ fun testOnDecorMaximizedOrRestored_togglesTaskSize() {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
+ onTaskOpening(decor.mTaskInfo)
+ val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
+ .let { captor ->
+ verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture())
+ return@let captor.value
+ }
+
+ maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test")
+
+ verify(mockDesktopTasksController).toggleDesktopTaskSize(decor.mTaskInfo)
+ }
+
+ @Test
+ fun testOnDecorMaximizedOrRestored_closesMenus() {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
+ onTaskOpening(decor.mTaskInfo)
+ val maxOrRestoreListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
+ .let { captor ->
+ verify(decor).setOnMaximizeOrRestoreClickListener(captor.capture())
+ return@let captor.value
+ }
+
+ maxOrRestoreListener.onClick(decor.mTaskInfo.taskId, "test")
+
+ verify(decor).closeHandleMenu()
+ verify(decor).closeMaximizeMenu()
+ }
+
+ @Test
+ fun testOnDecorSnappedLeft_snapResizes() {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
+ onTaskOpening(decor.mTaskInfo)
+ val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
+ .let { captor ->
+ verify(decor).setOnLeftSnapClickListener(captor.capture())
+ return@let captor.value
+ }
+
+ snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+
+ verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.LEFT)
+ }
+
+ @Test
+ fun testOnDecorSnappedLeft_closeMenus() {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
+ onTaskOpening(decor.mTaskInfo)
+ val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
+ .let { captor ->
+ verify(decor).setOnLeftSnapClickListener(captor.capture())
+ return@let captor.value
+ }
+
+ snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+
+ verify(decor).closeHandleMenu()
+ verify(decor).closeMaximizeMenu()
+ }
+
+ @Test
+ fun testOnDecorSnappedRight_snapResizes() {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
+ onTaskOpening(decor.mTaskInfo)
+ val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
+ .let { captor ->
+ verify(decor).setOnRightSnapClickListener(captor.capture())
+ return@let captor.value
+ }
+
+ snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+
+ verify(mockDesktopTasksController).snapToHalfScreen(decor.mTaskInfo, SnapPosition.RIGHT)
+ }
+
+ @Test
+ fun testOnDecorSnappedRight_closeMenus() {
+ val decor = setUpMockDecorationForTask(createTask(windowingMode = WINDOWING_MODE_FREEFORM))
+ onTaskOpening(decor.mTaskInfo)
+ val snapLeftListener = ArgumentCaptor.forClass(OnTaskActionClickListener::class.java)
+ .let { captor ->
+ verify(decor).setOnRightSnapClickListener(captor.capture())
+ return@let captor.value
+ }
+
+ snapLeftListener.onClick(decor.mTaskInfo.taskId, "test")
+
+ verify(decor).closeHandleMenu()
+ verify(decor).closeMaximizeMenu()
+ }
+
private fun onTaskOpening(task: RunningTaskInfo, leash: SurfaceControl = SurfaceControl()) {
desktopModeWindowDecorViewModel.onTaskOpening(
task,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 46c1589..36e8a46 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -24,9 +24,14 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
+import static com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.CLOSE_MAXIMIZE_MENU_DELAY_MS;
import static com.google.common.truth.Truth.assertThat;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.doReturn;
@@ -38,11 +43,13 @@
import android.app.ActivityManager;
import android.content.ComponentName;
+import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.PointF;
import android.os.Handler;
import android.os.SystemProperties;
import android.platform.test.annotations.DisableFlags;
@@ -62,6 +69,7 @@
import android.view.WindowManager;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest;
@@ -76,6 +84,10 @@
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.shared.DesktopModeStatus;
import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
+import com.android.wm.shell.windowdecor.common.OnTaskActionClickListener;
+
+import kotlin.Unit;
+import kotlin.jvm.functions.Function1;
import org.junit.After;
import org.junit.Before;
@@ -84,6 +96,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.quality.Strictness;
@@ -112,8 +125,6 @@
@Mock
private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock
- private Handler mMockHandler;
- @Mock
private Choreographer mMockChoreographer;
@Mock
private SyncTransactionQueue mMockSyncQueue;
@@ -131,13 +142,18 @@
private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
@Mock
private TypedArray mMockRoundedCornersRadiusArray;
-
@Mock
private TestTouchEventListener mMockTouchEventListener;
@Mock
private DesktopModeWindowDecoration.ExclusionRegionListener mMockExclusionRegionListener;
@Mock
private PackageManager mMockPackageManager;
+ @Mock
+ private Handler mMockHandler;
+ @Captor
+ private ArgumentCaptor<Function1<Boolean, Unit>> mOnMaxMenuHoverChangeListener;
+ @Captor
+ private ArgumentCaptor<Runnable> mCloseMaxMenuRunnable;
private final InsetsState mInsetsState = new InsetsState();
private SurfaceControl.Transaction mMockTransaction;
@@ -459,6 +475,92 @@
verify(mMockHandler).removeCallbacks(runnableArgument.getValue());
}
+ @Test
+ public void createMaximizeMenu_showsMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final MaximizeMenu menu = mock(MaximizeMenu.class);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ new FakeMaximizeMenuFactory(menu));
+ assertFalse(decoration.isMaximizeMenuActive());
+
+ createMaximizeMenu(decoration, menu);
+
+ assertTrue(decoration.isMaximizeMenuActive());
+ }
+
+ @Test
+ public void maximizeMenu_unHoversMenu_schedulesCloseMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final MaximizeMenu menu = mock(MaximizeMenu.class);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ new FakeMaximizeMenuFactory(menu));
+ decoration.setAppHeaderMaximizeButtonHovered(false);
+ createMaximizeMenu(decoration, menu);
+
+ mOnMaxMenuHoverChangeListener.getValue().invoke(false);
+
+ verify(mMockHandler)
+ .postDelayed(mCloseMaxMenuRunnable.capture(), eq(CLOSE_MAXIMIZE_MENU_DELAY_MS));
+
+ mCloseMaxMenuRunnable.getValue().run();
+ verify(menu).close();
+ assertFalse(decoration.isMaximizeMenuActive());
+ }
+
+ @Test
+ public void maximizeMenu_unHoversButton_schedulesCloseMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final MaximizeMenu menu = mock(MaximizeMenu.class);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ new FakeMaximizeMenuFactory(menu));
+ decoration.setAppHeaderMaximizeButtonHovered(true);
+ createMaximizeMenu(decoration, menu);
+
+ decoration.setAppHeaderMaximizeButtonHovered(false);
+
+ verify(mMockHandler)
+ .postDelayed(mCloseMaxMenuRunnable.capture(), eq(CLOSE_MAXIMIZE_MENU_DELAY_MS));
+
+ mCloseMaxMenuRunnable.getValue().run();
+ verify(menu).close();
+ assertFalse(decoration.isMaximizeMenuActive());
+ }
+
+ @Test
+ public void maximizeMenu_hoversMenu_cancelsCloseMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final MaximizeMenu menu = mock(MaximizeMenu.class);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ new FakeMaximizeMenuFactory(menu));
+ createMaximizeMenu(decoration, menu);
+
+ mOnMaxMenuHoverChangeListener.getValue().invoke(true);
+
+ verify(mMockHandler).removeCallbacks(any());
+ }
+
+ @Test
+ public void maximizeMenu_hoversButton_cancelsCloseMenu() {
+ final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(true /* visible */);
+ final MaximizeMenu menu = mock(MaximizeMenu.class);
+ final DesktopModeWindowDecoration decoration = createWindowDecoration(taskInfo,
+ new FakeMaximizeMenuFactory(menu));
+ createMaximizeMenu(decoration, menu);
+
+ decoration.setAppHeaderMaximizeButtonHovered(true);
+
+ verify(mMockHandler).removeCallbacks(any());
+ }
+
+ private void createMaximizeMenu(DesktopModeWindowDecoration decoration, MaximizeMenu menu) {
+ final OnTaskActionClickListener l = (taskId, tag) -> {};
+ decoration.setOnMaximizeOrRestoreClickListener(l);
+ decoration.setOnLeftSnapClickListener(l);
+ decoration.setOnRightSnapClickListener(l);
+ decoration.createMaximizeMenu();
+ verify(menu).show(any(), any(), any(), mOnMaxMenuHoverChangeListener.capture());
+ }
+
private void fillRoundedCornersResources(int fillValue) {
when(mMockRoundedCornersRadiusArray.getDimensionPixelSize(anyInt(), anyInt()))
.thenReturn(fillValue);
@@ -479,12 +581,19 @@
private DesktopModeWindowDecoration createWindowDecoration(
ActivityManager.RunningTaskInfo taskInfo) {
- DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
+ return createWindowDecoration(taskInfo, new FakeMaximizeMenuFactory());
+ }
+
+ private DesktopModeWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo,
+ MaximizeMenuFactory maximizeMenuFactory) {
+ final DesktopModeWindowDecoration windowDecor = new DesktopModeWindowDecoration(mContext,
mMockDisplayController, mMockShellTaskOrganizer, taskInfo, mMockSurfaceControl,
mMockHandler, mMockChoreographer, mMockSyncQueue, mMockRootTaskDisplayAreaOrganizer,
SurfaceControl.Builder::new, mMockTransactionSupplier,
WindowContainerTransaction::new, SurfaceControl::new,
- mMockSurfaceControlViewHostFactory);
+ mMockSurfaceControlViewHostFactory,
+ maximizeMenuFactory);
windowDecor.setCaptionListeners(mMockTouchEventListener, mMockTouchEventListener,
mMockTouchEventListener, mMockTouchEventListener);
windowDecor.setExclusionRegionListener(mMockExclusionRegionListener);
@@ -541,4 +650,27 @@
return false;
}
}
+
+ private static final class FakeMaximizeMenuFactory implements MaximizeMenuFactory {
+ private final MaximizeMenu mMaximizeMenu;
+
+ FakeMaximizeMenuFactory() {
+ this(mock(MaximizeMenu.class));
+ }
+
+ FakeMaximizeMenuFactory(MaximizeMenu menu) {
+ mMaximizeMenu = menu;
+ }
+
+ @NonNull
+ @Override
+ public MaximizeMenu create(@NonNull SyncTransactionQueue syncQueue,
+ @NonNull RootTaskDisplayAreaOrganizer rootTdaOrganizer,
+ @NonNull DisplayController displayController,
+ @NonNull ActivityManager.RunningTaskInfo taskInfo,
+ @NonNull Context decorWindowContext, @NonNull PointF menuPosition,
+ @NonNull Supplier<SurfaceControl.Transaction> transactionSupplier) {
+ return mMaximizeMenu;
+ }
+ }
}
diff --git a/media/java/android/media/AudioManagerInternal.java b/media/java/android/media/AudioManagerInternal.java
index c263245..fd71f86 100644
--- a/media/java/android/media/AudioManagerInternal.java
+++ b/media/java/android/media/AudioManagerInternal.java
@@ -44,8 +44,9 @@
* Add the UID for a new assistant service
*
* @param uid UID of the newly available assistants
+ * @param owningUid UID of the actual assistant app, if {@code uid} is a isolated proc
*/
- public abstract void addAssistantServiceUid(int uid);
+ public abstract void addAssistantServiceUid(int uid, int owningUid);
/**
* Remove the UID for an existing assistant service
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/media/jni/Android.bp b/media/jni/Android.bp
index e619e1c..7f487e5 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -79,7 +79,6 @@
"libcamera_client",
"libmtp",
"libpiex",
- "libprocessgroup",
"libandroidfw",
"libhidlallocatorutils",
"libhidlbase",
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
index 00068bd..102d21a 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/helpers/CameraTestUtils.java
@@ -16,6 +16,8 @@
package com.android.mediaframeworktest.helpers;
+import android.content.AttributionSourceState;
+import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
@@ -2227,4 +2229,24 @@
else
return new Size(width, height);
}
+
+ /**
+ * Constructs an AttributionSourceState with only the uid, pid, and deviceId fields set
+ *
+ * <p>This method is a temporary stopgap in the transition to using AttributionSource. Currently
+ * AttributionSourceState is only used as a vehicle for passing deviceId, uid, and pid
+ * arguments.</p>
+ */
+ public static AttributionSourceState getClientAttribution(Context context) {
+ // TODO: Send the full contextAttribution over aidl, remove USE_CALLING_*
+ AttributionSourceState contextAttribution =
+ context.getAttributionSource().asState();
+ AttributionSourceState clientAttribution =
+ new AttributionSourceState();
+ clientAttribution.uid = -1; // USE_CALLING_UID
+ clientAttribution.pid = -1; // USE_CALLING_PID
+ clientAttribution.deviceId = contextAttribution.deviceId;
+ clientAttribution.next = new AttributionSourceState[0];
+ return clientAttribution;
+ }
}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 353366d..ad3374a 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -19,6 +19,7 @@
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.content.Context.DEVICE_ID_DEFAULT;
+import android.content.AttributionSourceState;
import android.hardware.CameraInfo;
import android.hardware.ICamera;
import android.hardware.ICameraClient;
@@ -38,6 +39,8 @@
import androidx.test.filters.SmallTest;
+import com.android.mediaframeworktest.helpers.CameraTestUtils;
+
/**
* <p>
* Junit / Instrumentation test case for the camera2 api
@@ -78,8 +81,10 @@
@SmallTest
public void testNumberOfCameras() throws Exception {
+ AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext);
+ clientAttribution.deviceId = DEVICE_ID_DEFAULT;
int numCameras = mUtils.getCameraService().getNumberOfCameras(CAMERA_TYPE_ALL,
- DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
+ clientAttribution, DEVICE_POLICY_DEFAULT);
assertTrue("At least this many cameras: " + mUtils.getGuessedNumCameras(),
numCameras >= mUtils.getGuessedNumCameras());
Log.v(TAG, "Number of cameras " + numCameras);
@@ -87,9 +92,11 @@
@SmallTest
public void testCameraInfo() throws Exception {
+ AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext);
+ clientAttribution.deviceId = DEVICE_ID_DEFAULT;
for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
CameraInfo info = mUtils.getCameraService().getCameraInfo(cameraId,
- ICameraService.ROTATION_OVERRIDE_NONE, DEVICE_ID_DEFAULT,
+ ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution,
DEVICE_POLICY_DEFAULT);
assertTrue("Facing was not set for camera " + cameraId, info.info.facing != -1);
assertTrue("Orientation was not set for camera " + cameraId,
@@ -154,6 +161,10 @@
@SmallTest
public void testConnect() throws Exception {
+ AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext);
+ clientAttribution.deviceId = DEVICE_ID_DEFAULT;
+ clientAttribution.uid = ICameraService.USE_CALLING_UID;
+ clientAttribution.pid = ICameraService.USE_CALLING_PID;
for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
ICameraClient dummyCallbacks = new DummyCameraClient();
@@ -162,12 +173,10 @@
ICamera cameraUser = mUtils.getCameraService()
.connect(dummyCallbacks, cameraId, clientPackageName,
- ICameraService.USE_CALLING_UID,
- ICameraService.USE_CALLING_PID,
getContext().getApplicationInfo().targetSdkVersion,
ICameraService.ROTATION_OVERRIDE_NONE,
/*forceSlowJpegMode*/false,
- DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
+ clientAttribution, DEVICE_POLICY_DEFAULT);
assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
Log.v(TAG, String.format("Camera %s connected", cameraId));
@@ -260,14 +269,18 @@
String clientPackageName = getContext().getPackageName();
String clientAttributionTag = getContext().getAttributionTag();
+ AttributionSourceState clientAttribution =
+ CameraTestUtils.getClientAttribution(mContext);
+ clientAttribution.deviceId = DEVICE_ID_DEFAULT;
+ clientAttribution.uid = ICameraService.USE_CALLING_UID;
ICameraDeviceUser cameraUser =
mUtils.getCameraService().connectDevice(
dummyCallbacks, String.valueOf(cameraId),
clientPackageName, clientAttributionTag,
- ICameraService.USE_CALLING_UID, 0 /*oomScoreOffset*/,
+ 0 /*oomScoreOffset*/,
getContext().getApplicationInfo().targetSdkVersion,
- ICameraService.ROTATION_OVERRIDE_NONE, DEVICE_ID_DEFAULT,
+ ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution,
DEVICE_POLICY_DEFAULT);
assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index 6cf2a41..0ab1ee9 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -27,6 +27,7 @@
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
+import android.content.AttributionSourceState;
import android.graphics.ImageFormat;
import android.graphics.SurfaceTexture;
import android.hardware.ICameraService;
@@ -54,6 +55,7 @@
import androidx.test.filters.SmallTest;
import com.android.mediaframeworktest.MediaFrameworkIntegrationTestRunner;
+import com.android.mediaframeworktest.helpers.CameraTestUtils;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
@@ -245,10 +247,14 @@
mMockCb = spy(dummyCallbacks);
+ AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext);
+ clientAttribution.deviceId = DEVICE_ID_DEFAULT;
+ clientAttribution.uid = ICameraService.USE_CALLING_UID;
+
mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
- clientPackageName, clientAttributionTag, ICameraService.USE_CALLING_UID,
+ clientPackageName, clientAttributionTag,
/*oomScoreOffset*/0, getContext().getApplicationInfo().targetSdkVersion,
- ICameraService.ROTATION_OVERRIDE_NONE, DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
+ ICameraService.ROTATION_OVERRIDE_NONE, clientAttribution, DEVICE_POLICY_DEFAULT);
assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
@@ -414,10 +420,13 @@
@SmallTest
public void testCameraCharacteristics() throws RemoteException {
+ AttributionSourceState clientAttribution = CameraTestUtils.getClientAttribution(mContext);
+ clientAttribution.deviceId = DEVICE_ID_DEFAULT;
+
CameraMetadataNative info = mUtils.getCameraService().getCameraCharacteristics(mCameraId,
getContext().getApplicationInfo().targetSdkVersion,
ICameraService.ROTATION_OVERRIDE_NONE,
- DEVICE_ID_DEFAULT, DEVICE_POLICY_DEFAULT);
+ clientAttribution, DEVICE_POLICY_DEFAULT);
assertFalse(info.isEmpty());
assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
diff --git a/native/android/performance_hint.cpp b/native/android/performance_hint.cpp
index 7e5bef1..e91c7a9 100644
--- a/native/android/performance_hint.cpp
+++ b/native/android/performance_hint.cpp
@@ -244,6 +244,12 @@
ALOGE("%s: targetDurationNanos must be positive", __FUNCTION__);
return EINVAL;
}
+ {
+ std::scoped_lock lock(sHintMutex);
+ if (mTargetDurationNanos == targetDurationNanos) {
+ return 0;
+ }
+ }
ndk::ScopedAStatus ret = mHintSession->updateTargetWorkDuration(targetDurationNanos);
if (!ret.isOk()) {
ALOGE("%s: HintSession updateTargetWorkDuration failed: %s", __FUNCTION__,
diff --git a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
index 78a5357..d19fa98 100644
--- a/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
+++ b/native/android/tests/performance_hint/PerformanceHintNativeTest.cpp
@@ -159,6 +159,10 @@
int result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos);
EXPECT_EQ(0, result);
+ // subsequent call with same target should be ignored but return no error
+ result = APerformanceHint_updateTargetWorkDuration(session, targetDurationNanos);
+ EXPECT_EQ(0, result);
+
usleep(2); // Sleep for longer than preferredUpdateRateNanos.
int64_t actualDurationNanos = 20;
std::vector<int64_t> actualDurations;
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index b242a76..95945d7 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -110,3 +110,11 @@
bug: "321311407"
}
+flag {
+ name: "nfc_persist_log"
+ is_exported: true
+ namespace: "nfc"
+ description: "Enable NFC persistent log support"
+ bug: "321310044"
+}
+
diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig
index 4ac3e67..8666584 100644
--- a/packages/SettingsLib/aconfig/settingslib.aconfig
+++ b/packages/SettingsLib/aconfig/settingslib.aconfig
@@ -64,13 +64,6 @@
}
flag {
- name: "allow_all_widgets_on_lockscreen_by_default"
- namespace: "systemui"
- description: "Allow all widgets on the lock screen by default."
- bug: "328261690"
-}
-
-flag {
name: "enable_determining_advanced_details_header_with_metadata"
namespace: "pixel_cross_device_control"
description: "Use metadata instead of device type to determine whether a bluetooth device should use advanced details header."
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/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
index df0e618..8868837 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -23,19 +23,8 @@
import android.telephony.UiccSlotInfo;
import android.telephony.UiccSlotMapping;
-import java.util.List;
-
public class DataServiceUtils {
- public static <T> boolean shouldUpdateEntityList(List<T> oldList, List<T> newList) {
- if ((oldList != null &&
- (newList.isEmpty() || !newList.equals(oldList)))
- || (!newList.isEmpty() && oldList == null)) {
- return true;
- }
- return false;
- }
-
/**
* Represents columns of the MobileNetworkInfoData table, define these columns from
* {@see MobileNetworkUtils} or relevant common APIs.
@@ -52,73 +41,16 @@
public static final String COLUMN_ID = "subId";
/**
- * The name of the contact discovery enabled state column,
- * {@see MobileNetworkUtils#isContactDiscoveryEnabled(Context, int)}.
- */
- public static final String COLUMN_IS_CONTACT_DISCOVERY_ENABLED =
- "isContactDiscoveryEnabled";
-
- /**
- * The name of the contact discovery visible state column,
- * {@see MobileNetworkUtils#isContactDiscoveryEnabled(Context, int)}.
- */
- public static final String COLUMN_IS_CONTACT_DISCOVERY_VISIBLE =
- "isContactDiscoveryVisible";
-
- /**
* The name of the mobile network data state column,
* {@see MobileNetworkUtils#isMobileDataEnabled(Context)}.
*/
public static final String COLUMN_IS_MOBILE_DATA_ENABLED = "isMobileDataEnabled";
/**
- * The name of the CDMA option state column,
- * {@see MobileNetworkUtils#isCdmaOptions(Context, int)}.
- */
- public static final String COLUMN_IS_CDMA_OPTIONS = "isCdmaOptions";
-
- /**
- * The name of the GSM option state column,
- * {@see MobileNetworkUtils#isGsmOptions(Context, int)}.
- */
- public static final String COLUMN_IS_GSM_OPTIONS = "isGsmOptions";
-
- /**
- * The name of the world mode state column,
- * {@see MobileNetworkUtils#isWorldMode(Context, int)}.
- */
- public static final String COLUMN_IS_WORLD_MODE = "isWorldMode";
-
- /**
- * The name of the display network select options state column,
- * {@see MobileNetworkUtils#shouldDisplayNetworkSelectOptions(Context, int)}.
- */
- public static final String COLUMN_SHOULD_DISPLAY_NETWORK_SELECT_OPTIONS =
- "shouldDisplayNetworkSelectOptions";
-
- /**
- * The name of the TDSCDMA supported state column,
- * {@see MobileNetworkUtils#isTdscdmaSupported(Context, int)}.
- */
- public static final String COLUMN_IS_TDSCDMA_SUPPORTED = "isTdscdmaSupported";
-
- /**
- * The name of the active network is cellular state column,
- * {@see MobileNetworkUtils#activeNetworkIsCellular(Context)}.
- */
- public static final String COLUMN_ACTIVE_NETWORK_IS_CELLULAR = "activeNetworkIsCellular";
-
- /**
* The name of the show toggle for physicalSim state column,
* {@see SubscriptionUtil#showToggleForPhysicalSim(SubscriptionManager)}.
*/
public static final String COLUMN_SHOW_TOGGLE_FOR_PHYSICAL_SIM = "showToggleForPhysicalSim";
-
- /**
- * The name of the subscription's data roaming state column,
- * {@see TelephonyManager#isDataRoamingEnabled()}.
- */
- public static final String COLUMN_IS_DATA_ROAMING_ENABLED = "isDataRoamingEnabled";
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java
index e72346d..13f99e9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkInfoEntity.java
@@ -26,23 +26,11 @@
@Entity(tableName = DataServiceUtils.MobileNetworkInfoData.TABLE_NAME)
public class MobileNetworkInfoEntity {
- public MobileNetworkInfoEntity(@NonNull String subId, boolean isContactDiscoveryEnabled,
- boolean isContactDiscoveryVisible, boolean isMobileDataEnabled, boolean isCdmaOptions,
- boolean isGsmOptions, boolean isWorldMode, boolean shouldDisplayNetworkSelectOptions,
- boolean isTdscdmaSupported, boolean activeNetworkIsCellular,
- boolean showToggleForPhysicalSim, boolean isDataRoamingEnabled) {
+ public MobileNetworkInfoEntity(@NonNull String subId, boolean isMobileDataEnabled,
+ boolean showToggleForPhysicalSim) {
this.subId = subId;
- this.isContactDiscoveryEnabled = isContactDiscoveryEnabled;
- this.isContactDiscoveryVisible = isContactDiscoveryVisible;
this.isMobileDataEnabled = isMobileDataEnabled;
- this.isCdmaOptions = isCdmaOptions;
- this.isGsmOptions = isGsmOptions;
- this.isWorldMode = isWorldMode;
- this.shouldDisplayNetworkSelectOptions = shouldDisplayNetworkSelectOptions;
- this.isTdscdmaSupported = isTdscdmaSupported;
- this.activeNetworkIsCellular = activeNetworkIsCellular;
this.showToggleForPhysicalSim = showToggleForPhysicalSim;
- this.isDataRoamingEnabled = isDataRoamingEnabled;
}
@PrimaryKey
@@ -50,55 +38,18 @@
@NonNull
public String subId;
- @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_CONTACT_DISCOVERY_ENABLED)
- public boolean isContactDiscoveryEnabled;
-
- @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_CONTACT_DISCOVERY_VISIBLE)
- public boolean isContactDiscoveryVisible;
-
@ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_MOBILE_DATA_ENABLED)
public boolean isMobileDataEnabled;
- @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_CDMA_OPTIONS)
- public boolean isCdmaOptions;
-
- @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_GSM_OPTIONS)
- public boolean isGsmOptions;
-
- @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_WORLD_MODE)
- public boolean isWorldMode;
-
- @ColumnInfo(name =
- DataServiceUtils.MobileNetworkInfoData.COLUMN_SHOULD_DISPLAY_NETWORK_SELECT_OPTIONS)
- public boolean shouldDisplayNetworkSelectOptions;
-
- @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_TDSCDMA_SUPPORTED)
- public boolean isTdscdmaSupported;
-
- @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_ACTIVE_NETWORK_IS_CELLULAR)
- public boolean activeNetworkIsCellular;
-
@ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_SHOW_TOGGLE_FOR_PHYSICAL_SIM)
public boolean showToggleForPhysicalSim;
- @ColumnInfo(name = DataServiceUtils.MobileNetworkInfoData.COLUMN_IS_DATA_ROAMING_ENABLED)
- public boolean isDataRoamingEnabled;
-
@Override
public int hashCode() {
int result = 17;
result = 31 * result + subId.hashCode();
- result = 31 * result + Boolean.hashCode(isContactDiscoveryEnabled);
- result = 31 * result + Boolean.hashCode(isContactDiscoveryVisible);
result = 31 * result + Boolean.hashCode(isMobileDataEnabled);
- result = 31 * result + Boolean.hashCode(isCdmaOptions);
- result = 31 * result + Boolean.hashCode(isGsmOptions);
- result = 31 * result + Boolean.hashCode(isWorldMode);
- result = 31 * result + Boolean.hashCode(shouldDisplayNetworkSelectOptions);
- result = 31 * result + Boolean.hashCode(isTdscdmaSupported);
- result = 31 * result + Boolean.hashCode(activeNetworkIsCellular);
result = 31 * result + Boolean.hashCode(showToggleForPhysicalSim);
- result = 31 * result + Boolean.hashCode(isDataRoamingEnabled);
return result;
}
@@ -113,45 +64,18 @@
MobileNetworkInfoEntity info = (MobileNetworkInfoEntity) obj;
return TextUtils.equals(subId, info.subId)
- && isContactDiscoveryEnabled == info.isContactDiscoveryEnabled
- && isContactDiscoveryVisible == info.isContactDiscoveryVisible
&& isMobileDataEnabled == info.isMobileDataEnabled
- && isCdmaOptions == info.isCdmaOptions
- && isGsmOptions == info.isGsmOptions
- && isWorldMode == info.isWorldMode
- && shouldDisplayNetworkSelectOptions == info.shouldDisplayNetworkSelectOptions
- && isTdscdmaSupported == info.isTdscdmaSupported
- && activeNetworkIsCellular == info.activeNetworkIsCellular
- && showToggleForPhysicalSim == info.showToggleForPhysicalSim
- && isDataRoamingEnabled == info.isDataRoamingEnabled;
+ && showToggleForPhysicalSim == info.showToggleForPhysicalSim;
}
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append(" {MobileNetworkInfoEntity(subId = ")
.append(subId)
- .append(", isContactDiscoveryEnabled = ")
- .append(isContactDiscoveryEnabled)
- .append(", isContactDiscoveryVisible = ")
- .append(isContactDiscoveryVisible)
.append(", isMobileDataEnabled = ")
.append(isMobileDataEnabled)
- .append(", isCdmaOptions = ")
- .append(isCdmaOptions)
- .append(", isGsmOptions = ")
- .append(isGsmOptions)
- .append(", isWorldMode = ")
- .append(isWorldMode)
- .append(", shouldDisplayNetworkSelectOptions = ")
- .append(shouldDisplayNetworkSelectOptions)
- .append(", isTdscdmaSupported = ")
- .append(isTdscdmaSupported)
.append(", activeNetworkIsCellular = ")
- .append(activeNetworkIsCellular)
- .append(", showToggleForPhysicalSim = ")
.append(showToggleForPhysicalSim)
- .append(", isDataRoamingEnabled = ")
- .append(isDataRoamingEnabled)
.append(")}");
return builder.toString();
}
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/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index b37db16..1b9a09d 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -373,6 +373,8 @@
<!-- Listen to (dis-)connection of external displays and enable / disable them. -->
<uses-permission android:name="android.permission.MANAGE_DISPLAYS" />
+ <uses-permission android:name="android.permission.SET_BIOMETRIC_DIALOG_ADVANCED" />
+
<protected-broadcast android:name="com.android.settingslib.action.REGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settingslib.action.UNREGISTER_SLICE_RECEIVER" />
<protected-broadcast android:name="com.android.settings.flashlight.action.FLASHLIGHT_CHANGED" />
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 90885ab..0731616 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -217,6 +217,17 @@
}
flag {
+ name: "notification_group_hun_removal_animation_fix"
+ namespace: "systemui"
+ description: "Fix the lack of hun removal animation for group notifications"
+ "(not GROUP_ALERT_SUMMARY)"
+ bug: "343475993"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "scene_container"
namespace: "systemui"
description: "Enables the scene container framework go/flexiglass."
@@ -1021,13 +1032,6 @@
}
flag {
- name: "glanceable_hub_shortcut_button"
- namespace: "systemui"
- description: "Shows a button over the dream and lock screen to open the glanceable hub"
- bug: "339667383"
-}
-
-flag {
name: "glanceable_hub_gesture_handle"
namespace: "systemui"
description: "Shows a vertical bar at the right edge to indicate the user can swipe to open the glanceable hub"
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index 2ed0f6c..e02e5f8 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -155,7 +155,7 @@
val coroutineScope = rememberCoroutineScope()
val currentSceneKey: SceneKey by
viewModel.currentScene.collectAsStateWithLifecycle(CommunalScenes.Blank)
- val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false)
+ val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle()
val showGestureIndicator by
viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false)
val backgroundType by
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index 9c2127c..be51c1a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -484,6 +484,7 @@
rememberDragAndDropTargetState(
gridState = gridState,
contentListState = contentListState,
+ contentOffset = contentOffset,
updateDragPositionForRemove = updateDragPositionForRemove
)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
index 37fe798..9e6f22a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/DragAndDropTargetState.kt
@@ -41,7 +41,7 @@
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.android.systemui.communal.domain.model.CommunalContentModel
-import com.android.systemui.communal.ui.compose.extensions.plus
+import com.android.systemui.communal.ui.compose.extensions.firstItemAtOffset
import com.android.systemui.communal.util.WidgetPickerIntentUtils
import com.android.systemui.communal.util.WidgetPickerIntentUtils.getWidgetExtraFromIntent
import kotlinx.coroutines.CoroutineScope
@@ -57,6 +57,7 @@
@Composable
internal fun rememberDragAndDropTargetState(
gridState: LazyGridState,
+ contentOffset: Offset,
contentListState: ContentListState,
updateDragPositionForRemove: (offset: Offset) -> Boolean,
): DragAndDropTargetState {
@@ -70,6 +71,7 @@
remember(gridState, contentListState) {
DragAndDropTargetState(
state = gridState,
+ contentOffset = contentOffset,
contentListState = contentListState,
scope = scope,
autoScrollSpeed = autoScrollSpeed,
@@ -145,6 +147,7 @@
*/
internal class DragAndDropTargetState(
private val state: LazyGridState,
+ private val contentOffset: Offset,
private val contentListState: ContentListState,
private val scope: CoroutineScope,
private val autoScrollSpeed: MutableState<Float>,
@@ -214,8 +217,7 @@
return@let true
}
return false
- }
- ?: false
+ } ?: false
}
fun onEnded() {
@@ -249,10 +251,9 @@
}
private fun findTargetItem(dragEvent: DragEvent): LazyGridItemInfo? =
- state.layoutInfo.visibleItemsInfo.firstOrNull { item ->
- dragEvent.x.toInt() in item.offset.x..(item.offset + item.size).x &&
- dragEvent.y.toInt() in item.offset.y..(item.offset + item.size).y
- }
+ state.layoutInfo.visibleItemsInfo.firstItemAtOffset(
+ Offset(dragEvent.x, dragEvent.y) - contentOffset
+ )
private fun movePlaceholderTo(index: Int) {
val currentIndex = contentListState.list.indexOf(placeHolder)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
index 4555f13..c34fb38 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenLongPress.kt
@@ -19,9 +19,10 @@
package com.android.systemui.keyguard.ui.composable
import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.indication
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
@@ -33,12 +34,14 @@
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.input.pointer.pointerInput
import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.communal.ui.compose.extensions.detectLongPressGesture
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel
/** Container for lockscreen content that handles long-press to bring up the settings menu. */
@Composable
+// TODO(b/344879669): now that it's more generic than long-press, rename it.
fun LockscreenLongPress(
- viewModel: KeyguardLongPressViewModel,
+ viewModel: KeyguardTouchHandlingViewModel,
modifier: Modifier = Modifier,
content: @Composable BoxScope.(onSettingsMenuPlaces: (coordinates: Rect?) -> Unit) -> Unit,
) {
@@ -50,14 +53,17 @@
Box(
modifier =
modifier
- .combinedClickable(
- enabled = isEnabled,
- onLongClick = viewModel::onLongPress,
- onClick = {},
- interactionSource = interactionSource,
- // Passing null for the indication removes the ripple effect.
- indication = null,
- )
+ .pointerInput(isEnabled) {
+ if (isEnabled) {
+ detectLongPressGesture { viewModel.onLongPress() }
+ }
+ }
+ .pointerInput(Unit) {
+ detectTapGestures(
+ onTap = { viewModel.onClick(it.x, it.y) },
+ onDoubleTap = { viewModel.onDoubleClick() },
+ )
+ }
.pointerInput(settingsMenuBounds) {
awaitEachGesture {
val pointerInputChange = awaitFirstDown()
@@ -65,7 +71,9 @@
viewModel.onTouchedOutside()
}
}
- },
+ }
+ // Passing null for the indication removes the ripple effect.
+ .indication(interactionSource, null)
) {
content(setSettingsMenuBounds)
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
index 6b210af..210ca69 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/CommunalBlueprint.kt
@@ -43,7 +43,7 @@
@Composable
override fun SceneScope.Content(modifier: Modifier) {
LockscreenLongPress(
- viewModel = viewModel.longPress,
+ viewModel = viewModel.touchHandling,
modifier = modifier,
) { _ ->
Box(modifier.background(Color.Black)) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index a39fa64..0a4c6fd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -72,7 +72,7 @@
val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
LockscreenLongPress(
- viewModel = viewModel.longPress,
+ viewModel = viewModel.touchHandling,
modifier = modifier,
) { onSettingsMenuPlaced ->
Layout(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index c83f62c..065f2a2 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -74,7 +74,7 @@
val unfoldTranslations by viewModel.unfoldTranslations.collectAsStateWithLifecycle()
LockscreenLongPress(
- viewModel = viewModel.longPress,
+ viewModel = viewModel.touchHandling,
modifier = modifier,
) { onSettingsMenuPlaced ->
Layout(
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
index 44b0535..15032e0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SettingsMenuSection.kt
@@ -30,8 +30,8 @@
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.isVisible
import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
@@ -42,7 +42,7 @@
@Inject
constructor(
private val viewModel: KeyguardSettingsMenuViewModel,
- private val longPressViewModel: KeyguardLongPressViewModel,
+ private val touchHandlingViewModel: KeyguardTouchHandlingViewModel,
private val vibratorHelper: VibratorHelper,
private val activityStarter: ActivityStarter,
) {
@@ -69,7 +69,7 @@
KeyguardSettingsViewBinder.bind(
view = this,
viewModel = viewModel,
- longPressViewModel = longPressViewModel,
+ touchHandlingViewModel = touchHandlingViewModel,
rootViewModel = null,
vibratorHelper = vibratorHelper,
activityStarter = activityStarter,
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 6805888..2eea2f0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -21,6 +21,7 @@
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.scrollBy
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Spacer
@@ -233,6 +234,8 @@
// The height of the scrim visible on screen when it is in its resting (collapsed) state.
val minVisibleScrimHeight: () -> Float = { screenHeight - maxScrimTop() }
+ val isClickable by viewModel.isClickable.collectAsStateWithLifecycle()
+
// we are not scrolled to the top unless the scrim is at its maximum offset.
LaunchedEffect(viewModel, scrimOffset) {
snapshotFlow { scrimOffset.value >= 0f }
@@ -328,6 +331,9 @@
)
)
}
+ .thenIf(isClickable) {
+ Modifier.clickable(onClick = { viewModel.onEmptySpaceClicked() })
+ }
) {
// Creates a cutout in the background scrim in the shape of the notifications scrim.
// Only visible when notif scrim alpha < 1, during shade expansion.
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
index 73a624a..aca473d 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/BrightnessMirror.kt
@@ -18,8 +18,9 @@
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
@@ -39,6 +40,7 @@
viewModel: BrightnessMirrorViewModel,
qsSceneAdapter: QSSceneAdapter,
modifier: Modifier = Modifier,
+ measureFromContainer: Boolean = false,
) {
val isShowing by viewModel.isShowing.collectAsStateWithLifecycle()
val mirrorAlpha by
@@ -47,9 +49,22 @@
label = "alphaAnimationBrightnessMirrorShowing",
)
val mirrorOffsetAndSize by viewModel.locationAndSize.collectAsStateWithLifecycle()
- val offset = IntOffset(0, mirrorOffsetAndSize.yOffset)
+ val yOffset =
+ if (measureFromContainer) {
+ mirrorOffsetAndSize.yOffsetFromContainer
+ } else {
+ mirrorOffsetAndSize.yOffsetFromWindow
+ }
+ val offset = IntOffset(0, yOffset)
- Box(modifier = modifier.fillMaxSize().graphicsLayer { alpha = mirrorAlpha }) {
+ // Use unbounded=true as the full mirror (with paddings and background offset) may be larger
+ // than the space we have (but it will fit, because the brightness slider fits).
+ Box(
+ modifier =
+ modifier.fillMaxHeight().wrapContentWidth(unbounded = true).graphicsLayer {
+ alpha = mirrorAlpha
+ }
+ ) {
QuickSettingsTheme {
// The assumption for using this AndroidView is that there will be only one in view at
// a given time (which is a reasonable assumption). Because `QSSceneAdapter` (actually
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 0b57151..2d5d259 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -185,7 +185,11 @@
BrightnessMirror(
viewModel = viewModel.brightnessMirrorViewModel,
- qsSceneAdapter = viewModel.qsSceneAdapter
+ qsSceneAdapter = viewModel.qsSceneAdapter,
+ modifier =
+ Modifier.thenIf(cutoutLocation != CutoutLocation.CENTER) {
+ Modifier.displayCutoutPadding()
+ }
)
val shouldPunchHoleBehindScrim =
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt
index 924aa54..4eaacf3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/session/ui/composable/Session.kt
@@ -86,7 +86,7 @@
*/
@Composable
fun <T> Session.rememberSession(vararg inputs: Any?, key: String? = null, init: () -> T): T =
- rememberSession(key, inputs, init = init)
+ rememberSession(key, *inputs, init = init)
/**
* An explicit storage for remembering composable state outside of the lifetime of a composition.
@@ -151,7 +151,7 @@
vararg inputs: Any?,
key: String? = null,
): SaveableSession =
- rememberSaveable(inputs, SaveableSessionImpl.SessionSaver, key) { SaveableSessionImpl() }
+ rememberSaveable(*inputs, SaveableSessionImpl.SessionSaver, key) { SaveableSessionImpl() }
private class SessionImpl(
private val storage: SessionStorage = SessionStorage(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index edef5fb..4a6599a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -36,7 +36,6 @@
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.navigationBarsPadding
-import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -97,6 +96,7 @@
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.statusbar.phone.StatusBarLocation
import com.android.systemui.statusbar.phone.ui.StatusBarIconController
import com.android.systemui.statusbar.phone.ui.TintedIconManager
@@ -138,6 +138,7 @@
private val shadeSession: SaveableSession,
private val notificationStackScrollView: Lazy<NotificationScrollView>,
private val viewModel: ShadeSceneViewModel,
+ private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
private val tintedIconManagerFactory: TintedIconManager.Factory,
private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory,
private val statusBarIconController: StatusBarIconController,
@@ -157,6 +158,7 @@
ShadeScene(
notificationStackScrollView.get(),
viewModel = viewModel,
+ notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
createTintedIconManager = tintedIconManagerFactory::create,
createBatteryMeterViewController = batteryMeterViewControllerFactory::create,
statusBarIconController = statusBarIconController,
@@ -177,6 +179,7 @@
private fun SceneScope.ShadeScene(
notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneViewModel,
+ notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
statusBarIconController: StatusBarIconController,
@@ -191,6 +194,7 @@
SingleShade(
notificationStackScrollView = notificationStackScrollView,
viewModel = viewModel,
+ notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
@@ -203,6 +207,7 @@
SplitShade(
notificationStackScrollView = notificationStackScrollView,
viewModel = viewModel,
+ notificationsPlaceholderViewModel = notificationsPlaceholderViewModel,
createTintedIconManager = createTintedIconManager,
createBatteryMeterViewController = createBatteryMeterViewController,
statusBarIconController = statusBarIconController,
@@ -219,6 +224,7 @@
private fun SceneScope.SingleShade(
notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneViewModel,
+ notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
statusBarIconController: StatusBarIconController,
@@ -330,7 +336,7 @@
NotificationScrollingStack(
shadeSession = shadeSession,
stackScrollView = notificationStackScrollView,
- viewModel = viewModel.notifications,
+ viewModel = notificationsPlaceholderViewModel,
maxScrimTop = { maxNotifScrimTop.value },
shadeMode = ShadeMode.Single,
shouldPunchHoleBehindScrim = shouldPunchHoleBehindScrim,
@@ -354,7 +360,7 @@
}
NotificationStackCutoffGuideline(
stackScrollView = notificationStackScrollView,
- viewModel = viewModel.notifications,
+ viewModel = notificationsPlaceholderViewModel,
modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding()
)
}
@@ -364,6 +370,7 @@
private fun SceneScope.SplitShade(
notificationStackScrollView: NotificationScrollView,
viewModel: ShadeSceneViewModel,
+ notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
createTintedIconManager: (ViewGroup, StatusBarLocation) -> TintedIconManager,
createBatteryMeterViewController: (ViewGroup, StatusBarLocation) -> BatteryMeterViewController,
statusBarIconController: StatusBarIconController,
@@ -431,8 +438,10 @@
label = "alphaAnimationBrightnessMirrorContentHiding",
)
- viewModel.notifications.setAlphaForBrightnessMirror(contentAlpha)
- DisposableEffect(Unit) { onDispose { viewModel.notifications.setAlphaForBrightnessMirror(1f) } }
+ notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(contentAlpha)
+ DisposableEffect(Unit) {
+ onDispose { notificationsPlaceholderViewModel.setAlphaForBrightnessMirror(1f) }
+ }
val isMediaVisible by viewModel.isMediaVisible.collectAsStateWithLifecycle()
@@ -474,9 +483,9 @@
BrightnessMirror(
viewModel = viewModel.brightnessMirrorViewModel,
qsSceneAdapter = viewModel.qsSceneAdapter,
- // Need to remove the offset of the header height, as the mirror uses
- // the position of the Brightness slider in the window
- modifier = Modifier.offset(y = -ShadeHeader.Dimensions.CollapsedHeight)
+ // Need to use the offset measured from the container as the header
+ // has to be accounted for
+ measureFromContainer = true
)
Column(
verticalArrangement = Arrangement.Top,
@@ -533,7 +542,7 @@
NotificationScrollingStack(
shadeSession = shadeSession,
stackScrollView = notificationStackScrollView,
- viewModel = viewModel.notifications,
+ viewModel = notificationsPlaceholderViewModel,
maxScrimTop = { 0f },
shouldPunchHoleBehindScrim = false,
shouldReserveSpaceForNavBar = false,
@@ -548,7 +557,7 @@
}
NotificationStackCutoffGuideline(
stackScrollView = notificationStackScrollView,
- viewModel = viewModel.notifications,
+ viewModel = notificationsPlaceholderViewModel,
modifier = Modifier.align(Alignment.BottomCenter).navigationBarsPadding()
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
index 630bcd6..7ebc224 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandlerTest.java
@@ -21,6 +21,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -49,6 +50,7 @@
import com.android.systemui.ambient.touch.scrim.ScrimController;
import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.FakeUserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.shared.system.InputChannelCompat;
@@ -113,6 +115,9 @@
LockPatternUtils mLockPatternUtils;
@Mock
+ ActivityStarter mActivityStarter;
+
+ @Mock
Region mRegion;
@Captor
@@ -148,7 +153,8 @@
mFlingAnimationUtilsClosing,
TOUCH_REGION,
MIN_BOUNCER_HEIGHT,
- mUiEventLogger);
+ mUiEventLogger,
+ mActivityStarter);
when(mScrimManager.getCurrentController()).thenReturn(mScrimController);
when(mValueAnimatorCreator.create(anyFloat(), anyFloat())).thenReturn(mValueAnimator);
@@ -397,7 +403,12 @@
.isTrue();
// We should not expand since the keyguard is not secure
verify(mScrimController, never()).expand(any());
- // Since we are swiping up, we should wake from dreams.
+
+ // Since we are swiping up, we should dismiss the keyguard and wake from dreams.
+ ArgumentCaptor<Runnable> dismissKeyguardRunnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(mActivityStarter).executeRunnableDismissingKeyguard(
+ dismissKeyguardRunnable.capture(), isNull(), eq(true), eq(true), eq(false));
+ dismissKeyguardRunnable.getValue().run();
verify(mCentralSurfaces).awakenDreams();
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 60b48f2..242e822 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -324,7 +324,6 @@
fun showUdfpsOverlay_awake() =
testScope.runTest {
withReason(REASON_AUTH_KEYGUARD) {
- mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
powerRepository.updateWakefulness(
rawState = WakefulnessState.AWAKE,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
@@ -341,7 +340,6 @@
fun showUdfpsOverlay_whileGoingToSleep() =
testScope.runTest {
withReasonSuspend(REASON_AUTH_KEYGUARD) {
- mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.OFF,
to = KeyguardState.GONE,
@@ -370,7 +368,6 @@
fun showUdfpsOverlay_whileAsleep() =
testScope.runTest {
withReasonSuspend(REASON_AUTH_KEYGUARD) {
- mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.OFF,
to = KeyguardState.GONE,
@@ -399,7 +396,6 @@
fun neverRemoveViewThatHasNotBeenAdded() =
testScope.runTest {
withReasonSuspend(REASON_AUTH_KEYGUARD) {
- mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
controllerOverlay.show(udfpsController, overlayParams)
val view = controllerOverlay.getTouchOverlay()
view?.let {
@@ -414,7 +410,6 @@
fun showUdfpsOverlay_afterFinishedTransitioningToAOD() =
testScope.runTest {
withReasonSuspend(REASON_AUTH_KEYGUARD) {
- mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
keyguardTransitionRepository.sendTransitionSteps(
from = KeyguardState.OFF,
to = KeyguardState.GONE,
@@ -542,7 +537,6 @@
fun addViewPending_layoutIsNotUpdated() =
testScope.runTest {
withReasonSuspend(REASON_AUTH_KEYGUARD) {
- mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR)
// GIVEN going to sleep
@@ -580,7 +574,6 @@
fun updateOverlayParams_viewLayoutUpdated() =
testScope.runTest {
withReasonSuspend(REASON_AUTH_KEYGUARD) {
- mSetFlagsRule.enableFlags(Flags.FLAG_UDFPS_VIEW_PERFORMANCE)
powerRepository.updateWakefulness(
rawState = WakefulnessState.AWAKE,
lastWakeReason = WakeSleepReason.POWER_BUTTON,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
index fb2b33d..da40f64 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt
@@ -20,7 +20,6 @@
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE
import android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL
import android.app.admin.devicePolicyManager
-import android.appwidget.AppWidgetProviderInfo
import android.content.Intent
import android.content.pm.UserInfo
import android.os.UserManager.USER_TYPE_PROFILE_MANAGED
@@ -29,7 +28,6 @@
import android.provider.Settings
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.settingslib.flags.Flags.FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
@@ -183,42 +181,6 @@
)
}
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @Test
- fun hubShowsWidgetCategoriesSetByUser() =
- testScope.runTest {
- kosmos.fakeSettings.putIntForUser(
- CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
- AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
- PRIMARY_USER.id
- )
- val setting by collectLastValue(underTest.getWidgetCategories(PRIMARY_USER))
- assertThat(setting?.categories)
- .isEqualTo(AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN)
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB)
- @DisableFlags(FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT)
- @Test
- fun hubShowsKeyguardWidgetsByDefault() =
- testScope.runTest {
- val setting by collectLastValue(underTest.getWidgetCategories(PRIMARY_USER))
- assertThat(setting?.categories)
- .isEqualTo(AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
- }
-
- @EnableFlags(FLAG_COMMUNAL_HUB, FLAG_ALLOW_ALL_WIDGETS_ON_LOCKSCREEN_BY_DEFAULT)
- @Test
- fun hubShowsAllWidgetsByDefaultWhenFlagEnabled() =
- testScope.runTest {
- val setting by collectLastValue(underTest.getWidgetCategories(PRIMARY_USER))
- assertThat(setting?.categories)
- .isEqualTo(
- AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD +
- AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
- )
- }
-
@Test
fun backgroundType_defaultValue() =
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index d951cca..7b26db5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -36,7 +36,6 @@
import com.android.systemui.Flags.FLAG_COMMUNAL_HUB
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.broadcastDispatcher
-import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl
import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository
import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository
import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository
@@ -81,7 +80,6 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.fakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
@@ -915,14 +913,6 @@
)
runCurrent()
- // Keyguard widgets are allowed.
- kosmos.fakeSettings.putIntForUser(
- CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
- AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
- mainUser.id
- )
- runCurrent()
-
// When work profile is paused.
whenever(userManager.isQuietModeEnabled(eq(UserHandle.of(USER_INFO_WORK.id))))
.thenReturn(true)
@@ -956,93 +946,6 @@
}
@Test
- fun widgetContent_containsDisabledWidgets_whenCategoryNotAllowed() =
- testScope.runTest {
- // Communal available, and tutorial completed.
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-
- val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
- userRepository.setUserInfos(userInfos)
- userTracker.set(
- userInfos = userInfos,
- selectedUserIndex = 0,
- )
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
- runCurrent()
-
- // Widgets available.
- val widget1 =
- createWidgetWithCategory(1, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN)
- val widget2 =
- createWidgetWithCategory(2, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
- val widget3 =
- createWidgetWithCategory(3, AppWidgetProviderInfo.WIDGET_CATEGORY_SEARCHBOX)
- val widgets = listOf(widget1, widget2, widget3)
- widgetRepository.setCommunalWidgets(widgets)
-
- val widgetContent by collectLastValue(underTest.widgetContent)
- kosmos.fakeSettings.putIntForUser(
- CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
- AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD,
- mainUser.id
- )
-
- // Only the keyguard widget is enabled.
- assertThat(widgetContent).hasSize(3)
- assertThat(widgetContent!!.get(0))
- .isInstanceOf(CommunalContentModel.WidgetContent.DisabledWidget::class.java)
- assertThat(widgetContent!!.get(1))
- .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
- assertThat(widgetContent!!.get(2))
- .isInstanceOf(CommunalContentModel.WidgetContent.DisabledWidget::class.java)
- }
-
- @Test
- fun widgetContent_allEnabled_whenCategoryAllowed() =
- testScope.runTest {
- // Communal available, and tutorial completed.
- keyguardRepository.setKeyguardShowing(true)
- keyguardRepository.setKeyguardOccluded(false)
- tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
-
- val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK)
- userRepository.setUserInfos(userInfos)
- userTracker.set(
- userInfos = userInfos,
- selectedUserIndex = 0,
- )
- userRepository.setSelectedUserInfo(MAIN_USER_INFO)
- runCurrent()
-
- // Widgets available.
- val widget1 =
- createWidgetWithCategory(1, AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN)
- val widget2 =
- createWidgetWithCategory(2, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
- val widget3 =
- createWidgetWithCategory(3, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD)
- val widgets = listOf(widget1, widget2, widget3)
- widgetRepository.setCommunalWidgets(widgets)
-
- val widgetContent by collectLastValue(underTest.widgetContent)
- kosmos.fakeSettings.putIntForUser(
- CommunalSettingsRepositoryImpl.GLANCEABLE_HUB_CONTENT_SETTING,
- AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD or
- AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN,
- mainUser.id
- )
-
- // All widgets are enabled.
- assertThat(widgetContent).hasSize(3)
- widgetContent!!.forEach { model ->
- assertThat(model)
- .isInstanceOf(CommunalContentModel.WidgetContent.Widget::class.java)
- }
- }
-
- @Test
fun filterWidgets_whenDisallowedByDevicePolicyForWorkProfile() =
testScope.runTest {
// Keyguard showing, and tutorial completed.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 51991de..2694cab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -153,6 +153,7 @@
CommunalViewModel(
kosmos.testDispatcher,
testScope,
+ kosmos.testScope.backgroundScope,
context.resources,
kosmos.keyguardTransitionInteractor,
kosmos.keyguardInteractor,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
index 7b7d03b..7044895 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -72,6 +72,7 @@
/* animationController = */ notNull(),
/* fillInIntent = */ refEq(fillInIntent),
/* extraOptions = */ refEq(activityOptions.toBundle()),
+ /* customMessage */ isNull(),
)
}
@@ -93,6 +94,7 @@
/* animationController = */ isNull(),
/* fillInIntent = */ refEq(fillInIntent),
/* extraOptions = */ refEq(activityOptions.toBundle()),
+ /* customMessage */ isNull(),
)
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index 74eee9b..7d0f040 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -274,6 +274,29 @@
assertThat(couldClick).isFalse()
}
+ @Test
+ fun onTileClick_whileIdle_withQSTile_clicks() =
+ testWhileInState(QSLongPressEffect.State.IDLE) {
+ // GIVEN that a click was detected
+ val couldClick = longPressEffect.onTileClick()
+
+ // THEN the click is successful
+ assertThat(couldClick).isTrue()
+ }
+
+ @Test
+ fun onTileClick_whileIdle_withoutQSTile_cannotClick() =
+ testWhileInState(QSLongPressEffect.State.IDLE) {
+ // GIVEN that no QSTile has been set
+ longPressEffect.qsTile = null
+
+ // GIVEN that a click was detected
+ val couldClick = longPressEffect.onTileClick()
+
+ // THEN the click is not successful
+ assertThat(couldClick).isFalse()
+ }
+
private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 49d0399..26fcb23 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -90,7 +90,6 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = FakeUserTracker(),
- systemSettings = FakeSettings(),
broadcastDispatcher = fakeBroadcastDispatcher,
)
settings = FakeSettings()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
index 9ab1ac1..0f3e78b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManagerTest.kt
@@ -29,7 +29,6 @@
import com.android.systemui.settings.UserFileManager
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -81,7 +80,6 @@
context = context,
userFileManager = userFileManager,
userTracker = userTracker,
- systemSettings = FakeSettings(),
broadcastDispatcher = fakeBroadcastDispatcher,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 159ce36..8e109b4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -91,7 +91,6 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
- systemSettings = FakeSettings(),
broadcastDispatcher = fakeBroadcastDispatcher,
)
client1 = FakeCustomizationProviderClient()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 78a1167..2d77f4f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -148,7 +148,6 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
- systemSettings = FakeSettings(),
broadcastDispatcher = fakeBroadcastDispatcher,
)
val remoteUserSelectionManager =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
similarity index 92%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
index 9d34903..96b4b43 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractorTest.kt
@@ -32,6 +32,7 @@
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
+import com.android.systemui.shade.pulsingGestureListener
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.mock
@@ -53,14 +54,14 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
-class KeyguardLongPressInteractorTest : SysuiTestCase() {
+class KeyguardTouchHandlingInteractorTest : SysuiTestCase() {
private val kosmos =
testKosmos().apply {
this.accessibilityManagerWrapper = mock<AccessibilityManagerWrapper>()
this.uiEventLogger = mock<UiEventLoggerFake>()
}
- private lateinit var underTest: KeyguardLongPressInteractor
+ private lateinit var underTest: KeyguardTouchHandlingInteractor
private val logger = kosmos.uiEventLogger
private val testScope = kosmos.testScope
@@ -209,7 +210,7 @@
underTest.onLongPress()
assertThat(isMenuVisible).isTrue()
- advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
+ advanceTimeBy(KeyguardTouchHandlingInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
assertThat(isMenuVisible).isFalse()
}
@@ -224,11 +225,11 @@
assertThat(isMenuVisible).isTrue()
underTest.onMenuTouchGestureStarted()
- advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
+ advanceTimeBy(KeyguardTouchHandlingInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
assertThat(isMenuVisible).isTrue()
underTest.onMenuTouchGestureEnded(/* isClick= */ false)
- advanceTimeBy(KeyguardLongPressInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
+ advanceTimeBy(KeyguardTouchHandlingInteractor.DEFAULT_POPUP_AUTO_HIDE_TIMEOUT_MS)
assertThat(isMenuVisible).isFalse()
}
@@ -241,7 +242,7 @@
underTest.onLongPress()
verify(logger)
- .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN)
+ .log(KeyguardTouchHandlingInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_SHOWN)
}
@Test
@@ -254,7 +255,7 @@
underTest.onMenuTouchGestureEnded(/* isClick= */ true)
verify(logger)
- .log(KeyguardLongPressInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
+ .log(KeyguardTouchHandlingInteractor.LogEvents.LOCK_SCREEN_LONG_PRESS_POPUP_CLICKED)
}
@Test
@@ -288,7 +289,7 @@
// This needs to be re-created for each test outside of kosmos since the flag values are
// read during initialization to set up flows. Maybe there is a better way to handle that.
underTest =
- KeyguardLongPressInteractor(
+ KeyguardTouchHandlingInteractor(
appContext = mContext,
scope = testScope.backgroundScope,
transitionInteractor = kosmos.keyguardTransitionInteractor,
@@ -300,7 +301,8 @@
set(Flags.LOCK_SCREEN_LONG_PRESS_DIRECT_TO_WPP, isOpenWppDirectlyEnabled)
},
broadcastDispatcher = fakeBroadcastDispatcher,
- accessibilityManager = kosmos.accessibilityManagerWrapper
+ accessibilityManager = kosmos.accessibilityManagerWrapper,
+ pulsingGestureListener = kosmos.pulsingGestureListener,
)
setUpState()
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index 61d8216..4eb146d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -19,6 +19,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.platform.test.annotations.EnableFlags
+import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.SceneKey
@@ -61,6 +62,7 @@
@SmallTest
@RunWith(ParameterizedAndroidJunit4::class)
+@RunWithLooper
@EnableSceneContainer
class LockscreenSceneViewModelTest : SysuiTestCase() {
@@ -265,8 +267,8 @@
applicationScope = testScope.backgroundScope,
deviceEntryInteractor = kosmos.deviceEntryInteractor,
communalInteractor = kosmos.communalInteractor,
- longPress =
- KeyguardLongPressViewModel(
+ touchHandling =
+ KeyguardTouchHandlingViewModel(
interactor = mock(),
),
notifications = kosmos.notificationsPlaceholderViewModel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
new file mode 100644
index 0000000..1c3021e
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
@@ -0,0 +1,107 @@
+/*
+ * 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.qs.panels.ui.compose
+
+import androidx.compose.runtime.mutableStateOf
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DragAndDropStateTest : SysuiTestCase() {
+ private val listState = EditTileListState(TestEditTiles)
+ private val underTest = DragAndDropState(mutableStateOf(null), listState)
+
+ @Test
+ fun isMoving_returnsCorrectValue() {
+ // Asserts no tiles is moving
+ TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }
+
+ // Start the drag movement
+ val movingTileSpec = TestEditTiles[0].tileSpec
+ underTest.onStarted(movingTileSpec)
+
+ // Assert that the correct tile is marked as moving
+ TestEditTiles.forEach {
+ assertThat(underTest.isMoving(it.tileSpec)).isEqualTo(movingTileSpec == it.tileSpec)
+ }
+ }
+
+ @Test
+ fun onMoved_updatesList() {
+ val movingTileSpec = TestEditTiles[0].tileSpec
+
+ // Start the drag movement
+ underTest.onStarted(movingTileSpec)
+
+ // Move the tile to the end of the list
+ underTest.onMoved(listState.tiles[5].tileSpec)
+ assertThat(underTest.currentPosition()).isEqualTo(5)
+
+ // Move the tile to the middle of the list
+ underTest.onMoved(listState.tiles[2].tileSpec)
+ assertThat(underTest.currentPosition()).isEqualTo(2)
+ }
+
+ @Test
+ fun onDrop_resetsMovingTile() {
+ val movingTileSpec = TestEditTiles[0].tileSpec
+
+ // Start the drag movement
+ underTest.onStarted(movingTileSpec)
+
+ // Move the tile to the end of the list
+ underTest.onMoved(listState.tiles[5].tileSpec)
+
+ // Drop the tile
+ underTest.onDrop()
+
+ // Asserts no tiles is moving
+ TestEditTiles.forEach { assertThat(underTest.isMoving(it.tileSpec)).isFalse() }
+ }
+
+ companion object {
+ private fun createEditTile(tileSpec: String): EditTileViewModel {
+ return EditTileViewModel(
+ tileSpec = TileSpec.create(tileSpec),
+ icon = Icon.Resource(0, null),
+ label = Text.Loaded("unused"),
+ appName = null,
+ isCurrent = true,
+ availableEditActions = emptySet(),
+ )
+ }
+
+ private val TestEditTiles =
+ listOf(
+ createEditTile("tileA"),
+ createEditTile("tileB"),
+ createEditTile("tileC"),
+ createEditTile("tileD"),
+ createEditTile("tileE"),
+ createEditTile("tileF"),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
new file mode 100644
index 0000000..517b601
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.qs.panels.ui.compose
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class EditTileListStateTest : SysuiTestCase() {
+ val underTest = EditTileListState(TestEditTiles)
+
+ @Test
+ fun movingNonExistentTile_listUnchanged() {
+ underTest.move(TileSpec.create("other_tile"), TestEditTiles[0].tileSpec)
+
+ assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
+ }
+
+ @Test
+ fun movingTileToNonExistentTarget_listUnchanged() {
+ underTest.move(TestEditTiles[0].tileSpec, TileSpec.create("other_tile"))
+
+ assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
+ }
+
+ @Test
+ fun movingTileToItself_listUnchanged() {
+ underTest.move(TestEditTiles[0].tileSpec, TestEditTiles[0].tileSpec)
+
+ assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
+ }
+
+ @Test
+ fun movingTileToSameSection_listUpdates() {
+ // Move tile at index 0 to index 1. Tile 0 should remain current.
+ underTest.move(TestEditTiles[0].tileSpec, TestEditTiles[1].tileSpec)
+
+ // Assert the tiles 0 and 1 have changed places.
+ assertThat(underTest.tiles[0]).isEqualTo(TestEditTiles[1])
+ assertThat(underTest.tiles[1]).isEqualTo(TestEditTiles[0])
+
+ // Assert the rest of the list is unchanged
+ assertThat(underTest.tiles.subList(2, 5))
+ .containsExactly(*TestEditTiles.subList(2, 5).toTypedArray())
+ }
+
+ @Test
+ fun movingTileToDifferentSection_listAndTileUpdates() {
+ // Move tile at index 0 to index 3. Tile 0 should no longer be current.
+ underTest.move(TestEditTiles[0].tileSpec, TestEditTiles[3].tileSpec)
+
+ // Assert tile 0 is now at index 3 and is no longer current.
+ assertThat(underTest.tiles[3]).isEqualTo(TestEditTiles[0].copy(isCurrent = false))
+
+ // Assert previous tiles have shifted places
+ assertThat(underTest.tiles[0]).isEqualTo(TestEditTiles[1])
+ assertThat(underTest.tiles[1]).isEqualTo(TestEditTiles[2])
+ assertThat(underTest.tiles[2]).isEqualTo(TestEditTiles[3])
+
+ // Assert the rest of the list is unchanged
+ assertThat(underTest.tiles.subList(4, 5))
+ .containsExactly(*TestEditTiles.subList(4, 5).toTypedArray())
+ }
+
+ companion object {
+ private fun createEditTile(tileSpec: String, isCurrent: Boolean): EditTileViewModel {
+ return EditTileViewModel(
+ tileSpec = TileSpec.create(tileSpec),
+ icon = Icon.Resource(0, null),
+ label = Text.Loaded("unused"),
+ appName = null,
+ isCurrent = isCurrent,
+ availableEditActions = emptySet(),
+ )
+ }
+
+ private val TestEditTiles =
+ listOf(
+ createEditTile("tileA", true),
+ createEditTile("tileB", true),
+ createEditTile("tileC", true),
+ createEditTile("tileD", false),
+ createEditTile("tileE", false),
+ createEditTile("tileF", false),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
index e01ffa6..9249621 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.qs.ui.viewmodel
+import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.Back
@@ -65,6 +66,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@RunWithLooper
@EnableSceneContainer
class QuickSettingsSceneViewModelTest : SysuiTestCase() {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 412505d..cb4d96f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -48,16 +48,13 @@
import com.android.systemui.flags.EnableSceneContainer
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel
import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
import com.android.systemui.kosmos.testScope
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
import com.android.systemui.power.domain.interactor.powerInteractor
-import com.android.systemui.qs.footerActionsController
-import com.android.systemui.qs.footerActionsViewModelFactory
-import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.domain.startable.sceneContainerStartable
@@ -65,16 +62,14 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.scene.ui.viewmodel.SceneContainerViewModel
-import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
-import com.android.systemui.shade.ui.viewmodel.shadeHeaderViewModel
+import com.android.systemui.shade.ui.viewmodel.shadeSceneViewModel
import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository
import com.android.systemui.telephony.data.repository.fakeTelephonyRepository
import com.android.systemui.testKosmos
-import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -152,8 +147,8 @@
applicationScope = testScope.backgroundScope,
deviceEntryInteractor = deviceEntryInteractor,
communalInteractor = communalInteractor,
- longPress =
- KeyguardLongPressViewModel(
+ touchHandling =
+ KeyguardTouchHandlingViewModel(
interactor = mock(),
),
notifications = kosmos.notificationsPlaceholderViewModel,
@@ -167,7 +162,7 @@
private var bouncerSceneJob: Job? = null
- private val qsFlexiglassAdapter = FakeQSSceneAdapter(inflateDelegate = { mock() })
+ private val qsFlexiglassAdapter = kosmos.fakeQSSceneAdapter
private lateinit var emergencyAffordanceManager: EmergencyAffordanceManager
private lateinit var telecomManager: TelecomManager
@@ -200,20 +195,7 @@
bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
bouncerViewModel = kosmos.bouncerViewModel
- shadeSceneViewModel =
- ShadeSceneViewModel(
- applicationScope = testScope.backgroundScope,
- shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
- qsSceneAdapter = qsFlexiglassAdapter,
- notifications = kosmos.notificationsPlaceholderViewModel,
- brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
- mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
- shadeInteractor = kosmos.shadeInteractor,
- footerActionsController = kosmos.footerActionsController,
- footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
- sceneInteractor = sceneInteractor,
- unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor,
- )
+ shadeSceneViewModel = kosmos.shadeSceneViewModel
val startable = kosmos.sceneContainerStartable
startable.start()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt
index 6de7f40..13b61bc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflaterTest.kt
@@ -68,4 +68,27 @@
Assert.setTestThread(null)
}
+
+ @Test
+ fun inflate_frameHasPadding() {
+ Assert.setTestThread(Thread.currentThread())
+
+ val (frame, _) =
+ BrightnessMirrorInflater.inflate(
+ themedContext,
+ kosmos.brightnessSliderControllerFactory,
+ )
+
+ assertThat(frame.visibility).isEqualTo(View.VISIBLE)
+
+ val padding =
+ context.resources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+
+ assertThat(frame.paddingLeft).isEqualTo(padding)
+ assertThat(frame.paddingTop).isEqualTo(padding)
+ assertThat(frame.paddingRight).isEqualTo(padding)
+ assertThat(frame.paddingBottom).isEqualTo(padding)
+
+ Assert.setTestThread(null)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt
index 09fc6f9..90c11d4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/settings/brightness/ui/viewmodel/BrightnessMirrorViewModelTest.kt
@@ -16,10 +16,9 @@
package com.android.systemui.settings.brightness.ui.viewmodel
-import android.content.applicationContext
import android.content.res.mainResources
-import android.view.ContextThemeWrapper
import android.view.View
+import android.widget.FrameLayout
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -27,12 +26,10 @@
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor
-import com.android.systemui.settings.brightness.ui.binder.BrightnessMirrorInflater
import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.settings.brightness.ui.viewModel.LocationAndSize
import com.android.systemui.settings.brightnessSliderControllerFactory
import com.android.systemui.testKosmos
-import com.android.systemui.util.Assert
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
@@ -47,9 +44,6 @@
private val kosmos = testKosmos()
- private val themedContext =
- ContextThemeWrapper(kosmos.applicationContext, R.style.Theme_SystemUI_QuickSettings)
-
private val underTest =
with(kosmos) {
BrightnessMirrorViewModel(
@@ -76,7 +70,7 @@
}
@Test
- fun setLocationInWindow_correctLocationAndSize() =
+ fun locationInWindowAndContainer_correctLocationAndSize() =
with(kosmos) {
testScope.runTest {
val locationAndSize by collectLastValue(underTest.locationAndSize)
@@ -101,6 +95,7 @@
whenever(measuredHeight).thenReturn(height)
whenever(measuredWidth).thenReturn(width)
}
+ val yOffsetFromContainer = setContainerViewHierarchy(mockView)
underTest.setLocationAndSize(mockView)
@@ -108,7 +103,8 @@
.isEqualTo(
// Adjust for padding around the view
LocationAndSize(
- yOffset = y - padding,
+ yOffsetFromWindow = y - padding,
+ yOffsetFromContainer = yOffsetFromContainer - padding,
width = width + 2 * padding,
height = height + 2 * padding,
)
@@ -116,31 +112,20 @@
}
}
- @Test
- fun setLocationInWindow_paddingSetToRootView() =
- with(kosmos) {
- Assert.setTestThread(Thread.currentThread())
- val padding =
- mainResources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
+ private fun setContainerViewHierarchy(mockView: View): Int {
+ val rootView = FrameLayout(context)
+ val containerView = FrameLayout(context).apply { id = R.id.quick_settings_container }
+ val otherView = FrameLayout(context)
- val view = mock<View>()
+ rootView.addView(containerView)
+ containerView.addView(otherView)
+ otherView.addView(mockView)
- val (_, sliderController) =
- BrightnessMirrorInflater.inflate(
- themedContext,
- brightnessSliderControllerFactory,
- )
- underTest.setToggleSlider(sliderController)
+ containerView.setLeftTopRightBottom(1, /* top= */ 1, 1, 1)
+ otherView.setLeftTopRightBottom(0, /* top= */ 2, 0, 0)
+ whenever(mockView.parent).thenReturn(otherView)
+ whenever(mockView.top).thenReturn(3)
- underTest.setLocationAndSize(view)
-
- with(sliderController.rootView) {
- assertThat(paddingBottom).isEqualTo(padding)
- assertThat(paddingTop).isEqualTo(padding)
- assertThat(paddingLeft).isEqualTo(padding)
- assertThat(paddingRight).isEqualTo(padding)
- }
-
- Assert.setTestThread(null)
- }
+ return 2 + 3
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
index a0295c9..673d5ef 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt
@@ -36,28 +36,20 @@
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.data.repository.mediaFilterRepository
-import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
-import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
import com.android.systemui.media.controls.shared.model.MediaData
-import com.android.systemui.qs.footerActionsController
-import com.android.systemui.qs.footerActionsViewModelFactory
-import com.android.systemui.qs.ui.adapter.FakeQSSceneAdapter
+import com.android.systemui.qs.ui.adapter.fakeQSSceneAdapter
+import com.android.systemui.qs.ui.adapter.qsSceneAdapter
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.domain.resolver.homeSceneFamilyResolver
import com.android.systemui.scene.shared.model.SceneFamilies
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.shared.model.TransitionKeys.ToSplitShade
-import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
import com.android.systemui.shade.data.repository.shadeRepository
-import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.shade.domain.startable.shadeStartable
import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationsPlaceholderViewModel
import com.android.systemui.testKosmos
-import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
import com.android.systemui.unfold.fakeUnfoldTransitionProgressProvider
-import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import java.util.Locale
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -66,11 +58,8 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
-import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -83,32 +72,9 @@
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val shadeRepository by lazy { kosmos.shadeRepository }
+ private val qsSceneAdapter by lazy { kosmos.fakeQSSceneAdapter }
- private val qsSceneAdapter = FakeQSSceneAdapter({ mock() })
-
- private lateinit var underTest: ShadeSceneViewModel
-
- @Mock private lateinit var mediaDataManager: MediaDataManager
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
-
- underTest =
- ShadeSceneViewModel(
- applicationScope = testScope.backgroundScope,
- shadeHeaderViewModel = kosmos.shadeHeaderViewModel,
- qsSceneAdapter = qsSceneAdapter,
- notifications = kosmos.notificationsPlaceholderViewModel,
- brightnessMirrorViewModel = kosmos.brightnessMirrorViewModel,
- mediaCarouselInteractor = kosmos.mediaCarouselInteractor,
- shadeInteractor = kosmos.shadeInteractor,
- footerActionsViewModelFactory = kosmos.footerActionsViewModelFactory,
- footerActionsController = kosmos.footerActionsController,
- sceneInteractor = kosmos.sceneInteractor,
- unfoldTransitionInteractor = kosmos.unfoldTransitionInteractor,
- )
- }
+ private val underTest: ShadeSceneViewModel by lazy { kosmos.shadeSceneViewModel }
@Test
fun upTransitionSceneKey_deviceLocked_lockScreen() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
index cc3fdc5..23b28e3 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.statusbar.notification
+import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.compose.animation.scene.ObservableTransitionState
@@ -48,6 +49,7 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@RunWithLooper
@EnableSceneContainer
class NotificationStackAppearanceIntegrationTest : SysuiTestCase() {
@@ -171,6 +173,22 @@
}
@Test
+ fun shadeExpansion_idleOnQs() =
+ testScope.runTest {
+ val transitionState =
+ MutableStateFlow<ObservableTransitionState>(
+ ObservableTransitionState.Idle(currentScene = Scenes.QuickSettings)
+ )
+ sceneInteractor.setTransitionState(transitionState)
+ val expandFraction by collectLastValue(scrollViewModel.expandFraction)
+ assertThat(expandFraction).isEqualTo(1f)
+
+ fakeSceneDataSource.changeScene(toScene = Scenes.QuickSettings)
+ val isScrollable by collectLastValue(scrollViewModel.isScrollable)
+ assertThat(isScrollable).isFalse()
+ }
+
+ @Test
fun shadeExpansion_shadeToQs() =
testScope.runTest {
val transitionState =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
index ee9fd349..4944c8b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.stack.ui.viewmodel
+import android.testing.TestableLooper.RunWithLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
@@ -31,9 +32,10 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
+@RunWithLooper
class NotificationsPlaceholderViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
- private val underTest = kosmos.notificationsPlaceholderViewModel
+ private val underTest by lazy { kosmos.notificationsPlaceholderViewModel }
@Test
fun onBoundsChanged() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
index 5887f90..ccd78ee 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImplTest.kt
@@ -154,6 +154,25 @@
}
@Test
+ fun startPendingIntentDismissingKeyguard_withCustomMessage_dismissWithAction() {
+ val pendingIntent = mock(PendingIntent::class.java)
+ `when`(pendingIntent.isActivity).thenReturn(true)
+ `when`(keyguardStateController.isShowing).thenReturn(true)
+ `when`(deviceProvisionedController.isDeviceProvisioned).thenReturn(true)
+ val customMessage = "Custom unlock reason"
+
+ underTest.startPendingIntentDismissingKeyguard(
+ intent = pendingIntent,
+ dismissShade = true,
+ customMessage = customMessage
+ )
+ mainExecutor.runAllReady()
+
+ verify(statusBarKeyguardViewManager)
+ .dismissWithAction(any(), eq(null), anyBoolean(), eq(customMessage))
+ }
+
+ @Test
fun startPendingIntentMaybeDismissingKeyguard_keyguardShowing_showOverLs_launchAnimator() {
val pendingIntent = mock(PendingIntent::class.java)
val parent = FrameLayout(context)
@@ -466,6 +485,7 @@
animationController: ActivityTransitionAnimator.Controller?,
fillInIntent: Intent? = null,
extraOptions: Bundle? = null,
+ customMessage: String? = null,
) {
underTest.startPendingIntentDismissingKeyguard(
intent = intent,
@@ -475,6 +495,7 @@
showOverLockscreen = true,
fillInIntent = fillInIntent,
extraOptions = extraOptions,
+ customMessage = customMessage,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index 200e92e..7346323 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -26,6 +26,7 @@
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.os.Handler;
import android.platform.test.flag.junit.FlagsParameterization;
import android.testing.TestableLooper;
@@ -85,6 +86,8 @@
@Mock private DumpManager dumpManager;
private AvalancheController mAvalancheController;
+ @Mock private Handler mBgHandler;
+
private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone {
TestableHeadsUpManagerPhone(
Context context,
@@ -101,7 +104,8 @@
UiEventLogger uiEventLogger,
JavaAdapter javaAdapter,
ShadeInteractor shadeInteractor,
- AvalancheController avalancheController
+ AvalancheController avalancheController,
+ Handler bgHandler
) {
super(
context,
@@ -119,7 +123,8 @@
uiEventLogger,
javaAdapter,
shadeInteractor,
- avalancheController
+ avalancheController,
+ bgHandler
);
mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME;
mAutoDismissTime = TEST_AUTO_DISMISS_TIME;
@@ -142,7 +147,8 @@
mUiEventLogger,
mJavaAdapter,
mShadeInteractor,
- mAvalancheController
+ mAvalancheController,
+ mBgHandler
);
}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 7cf56aa..abb721a 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -84,14 +84,17 @@
* Similar to {@link #startPendingIntentMaybeDismissingKeyguard(PendingIntent, Runnable,
* ActivityTransitionAnimator.Controller)}, but also specifies a fill-in intent and extra
* option that could be used to populate the pending intent and launch the activity. This also
- * allows the caller to avoid dismissing the shade.
+ * allows the caller to avoid dismissing the shade. An optional custom message can be set as
+ * the unlock reason in the alternate bouncer.
*/
void startPendingIntentMaybeDismissingKeyguard(PendingIntent intent,
boolean dismissShade,
@Nullable Runnable intentSentUiThreadCallback,
@Nullable ActivityTransitionAnimator.Controller animationController,
@Nullable Intent fillInIntent,
- @Nullable Bundle extraOptions);
+ @Nullable Bundle extraOptions,
+ @Nullable String customMessage
+ );
/**
* The intent flag can be specified in startActivity().
@@ -134,14 +137,20 @@
void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel,
boolean afterKeyguardGone);
- /** Authenticates if needed and dismisses keyguard to execute an action. */
+ /**
+ * Authenticates if needed and dismisses keyguard to execute an action.
+ *
+ * TODO(b/348431835) Display the custom message in the new alternate bouncer, when the
+ * device_entry_udfps_refactor flag is enabled.
+ */
void dismissKeyguardThenExecute(OnDismissAction action, @Nullable Runnable cancel,
boolean afterKeyguardGone, @Nullable String customMessage);
/** Starts an activity and dismisses keyguard. */
void startActivityDismissingKeyguard(Intent intent,
boolean onlyProvisioned,
- boolean dismissShade);
+ boolean dismissShade,
+ @Nullable String customMessage);
/** Starts an activity and dismisses keyguard. */
void startActivityDismissingKeyguard(Intent intent,
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/drawable/ic_widgets.xml b/packages/SystemUI/res/drawable/ic_widgets.xml
deleted file mode 100644
index b21d047..0000000
--- a/packages/SystemUI/res/drawable/ic_widgets.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<!--
- ~ 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.
- -->
-
-<!-- go/gm2-icons, from gs_widgets_vd_theme_24.xml -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:tint="?attr/colorControlNormal"
- android:viewportHeight="960"
- android:viewportWidth="960">
- <path
- android:fillColor="@android:color/black"
- android:pathData="M666,520L440,294L666,68L892,294L666,520ZM120,440L120,120L440,120L440,440L120,440ZM520,840L520,520L840,520L840,840L520,840ZM120,840L120,520L440,520L440,840L120,840ZM200,360L360,360L360,200L200,200L200,360ZM667,408L780,295L667,182L554,295L667,408ZM600,760L760,760L760,600L600,600L600,760ZM200,760L360,760L360,600L200,600L200,760ZM360,360L360,360L360,360L360,360L360,360ZM554,295L554,295L554,295L554,295L554,295ZM360,600L360,600L360,600L360,600L360,600ZM600,600L600,600L600,600L600,600L600,600Z" />
-</vector>
\ 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/layout/dream_overlay_open_hub_chip.xml b/packages/SystemUI/res/layout/dream_overlay_open_hub_chip.xml
deleted file mode 100644
index be063a9..0000000
--- a/packages/SystemUI/res/layout/dream_overlay_open_hub_chip.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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.
--->
-<com.android.systemui.animation.view.LaunchableImageView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_height="@dimen/dream_overlay_bottom_affordance_height"
- android:layout_width="@dimen/dream_overlay_bottom_affordance_width"
- android:layout_gravity="bottom|start"
- android:padding="@dimen/dream_overlay_bottom_affordance_padding"
- android:scaleType="fitCenter"
- android:tint="?android:attr/textColorPrimary"
- android:src="@drawable/ic_widgets"
- android:contentDescription="@string/accessibility_action_open_communal_hub" />
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 177ba598..212dae2 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -233,6 +233,7 @@
<item type="id" name="smart_space_barrier_bottom" />
<item type="id" name="small_clock_guideline_top" />
<item type="id" name="weather_clock_date_and_icons_barrier_bottom" />
+ <item type="id" name="weather_clock_bc_smartspace_bottom" />
<item type="id" name="accessibility_actions_view" />
<!-- Privacy dialog -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3eacaa1..e92b942 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>
@@ -1194,6 +1193,8 @@
<string name="popup_on_dismiss_cta_tile_text">Long press to customize widgets</string>
<!-- Text for the button to configure widgets after long press. [CHAR LIMIT=50] -->
<string name="button_to_configure_widgets_text">Customize widgets</string>
+ <!-- Text for unlock reason on the bouncer before customizing widgets. [CHAR LIMIT=NONE] -->
+ <string name="unlock_reason_to_customize_widgets">Unlock to customize widgets</string>
<!-- Description for the App icon of disabled widget. [CHAR LIMIT=NONE] -->
<string name="icon_description_for_disabled_widget">App icon for disabled widget</string>
<!-- Description for the App icon of a package that is currently being installed. [CHAR LIMIT=NONE] -->
@@ -1394,7 +1395,7 @@
<!-- Text which is shown in the expanded notification shade when there are currently no notifications visible that the user hasn't already seen. [CHAR LIMIT=30] -->
<string name="no_unseen_notif_text">No new notifications</string>
- <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=30] -->
+ <!-- Title of heads up notification for adaptive notifications user education. [CHAR LIMIT=50] -->
<string name="adaptive_notification_edu_hun_title">Adaptive notifications is on</string>
<!-- Text of heads up notification for adaptive notifications user education. [CHAR LIMIT=100] -->
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index d5bc10a..c00007b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -22,7 +22,7 @@
import android.view.MotionEvent;
import com.android.systemui.shared.recents.ISystemUiProxy;
-// Next ID: 29
+// Next ID: 34
oneway interface IOverviewProxy {
void onActiveNavBarRegionChanges(in Region activeRegion) = 11;
@@ -83,6 +83,11 @@
void onSystemBarAttributesChanged(int displayId, int behavior) = 20;
/**
+ * Sent when {@link TaskbarDelegate#onTransitionModeUpdated} is called.
+ */
+ void onTransitionModeUpdated(int barMode, boolean checkBarModes) = 21;
+
+ /**
* Sent when the desired dark intensity of the nav buttons has changed
*/
void onNavButtonsDarkIntensityChanged(float darkIntensity) = 22;
@@ -101,4 +106,30 @@
* Sent when the task bar stash state is toggled.
*/
void onTaskbarToggled() = 27;
+
+ /**
+ * Sent when the wallpaper visibility is updated.
+ */
+ void updateWallpaperVisibility(int displayId, boolean visible) = 29;
+
+ /**
+ * Sent when {@link TaskbarDelegate#checkNavBarModes} is called.
+ */
+ void checkNavBarModes() = 30;
+
+ /**
+ * Sent when {@link TaskbarDelegate#finishBarAnimations} is called.
+ */
+ void finishBarAnimations() = 31;
+
+ /**
+ * Sent when {@link TaskbarDelegate#touchAutoDim} is called. {@param reset} is true, when auto
+ * dim is reset after a timeout.
+ */
+ void touchAutoDim(boolean reset) = 32;
+
+ /**
+ * Sent when {@link TaskbarDelegate#transitionTo} is called.
+ */
+ void transitionTo(int barMode, boolean animate) = 33;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/shared/src/com/android/systemui/shared/statusbar/phone/BarTransitions.java
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
rename to packages/SystemUI/shared/src/com/android/systemui/shared/statusbar/phone/BarTransitions.java
index f62a79f..c123306 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/statusbar/phone/BarTransitions.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -14,11 +14,13 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shared.statusbar.phone;
+import android.annotation.ColorInt;
import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
@@ -34,8 +36,6 @@
import android.view.View;
import com.android.app.animation.Interpolators;
-import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -44,6 +44,11 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_COLORS = false;
+ @ColorInt
+ private static final int SYSTEM_BAR_BACKGROUND_OPAQUE = Color.BLACK;
+ @ColorInt
+ private static final int SYSTEM_BAR_BACKGROUND_TRANSPARENT = Color.TRANSPARENT;
+
public static final int MODE_TRANSPARENT = 0;
public static final int MODE_SEMI_TRANSPARENT = 1;
public static final int MODE_TRANSLUCENT = 2;
@@ -183,11 +188,11 @@
mTransparent = 0x2f0000ff;
mWarning = 0xffff0000;
} else {
- mOpaque = context.getColor(R.color.system_bar_background_opaque);
+ mOpaque = SYSTEM_BAR_BACKGROUND_OPAQUE;
mSemiTransparent = context.getColor(
com.android.internal.R.color.system_bar_background_semi_transparent);
- mTransparent = context.getColor(R.color.system_bar_background_transparent);
- mWarning = Utils.getColorAttrDefaultColor(context, android.R.attr.colorError);
+ mTransparent = SYSTEM_BAR_BACKGROUND_TRANSPARENT;
+ mWarning = getColorAttrDefaultColor(context, android.R.attr.colorError, 0);
}
mGradient = context.getDrawable(gradientResourceId);
}
@@ -226,7 +231,7 @@
@Override
public void setTint(int color) {
PorterDuff.Mode targetMode = mTintFilter == null ? Mode.SRC_IN :
- mTintFilter.getMode();
+ mTintFilter.getMode();
if (mTintFilter == null || mTintFilter.getColor() != color) {
mTintFilter = new PorterDuffColorFilter(color, targetMode);
}
@@ -304,10 +309,13 @@
Interpolators.LINEAR.getInterpolation(t), 1));
mGradientAlpha = (int)(v * targetGradientAlpha + mGradientAlphaStart * (1 - v));
mColor = Color.argb(
- (int)(v * Color.alpha(targetColor) + Color.alpha(mColorStart) * (1 - v)),
- (int)(v * Color.red(targetColor) + Color.red(mColorStart) * (1 - v)),
- (int)(v * Color.green(targetColor) + Color.green(mColorStart) * (1 - v)),
- (int)(v * Color.blue(targetColor) + Color.blue(mColorStart) * (1 - v)));
+ (int) (v * Color.alpha(targetColor) + Color.alpha(mColorStart) * (1
+ - v)),
+ (int) (v * Color.red(targetColor) + Color.red(mColorStart) * (1 - v)),
+ (int) (v * Color.green(targetColor) + Color.green(mColorStart) * (1
+ - v)),
+ (int) (v * Color.blue(targetColor) + Color.blue(mColorStart) * (1
+ - v)));
}
}
if (mGradientAlpha > 0) {
@@ -332,4 +340,13 @@
}
}
}
+
+ /** Get color styled attribute {@code attr}, default to {@code defValue} if not found. */
+ @ColorInt
+ public static int getColorAttrDefaultColor(Context context, int attr, @ColorInt int defValue) {
+ TypedArray ta = context.obtainStyledAttributes(new int[] {attr});
+ @ColorInt int colorAccent = ta.getColor(0, defValue);
+ ta.recycle();
+ return colorAccent;
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index c0c8b75..a614fc1 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -124,6 +124,8 @@
public static final long SYSUI_STATE_SHORTCUT_HELPER_SHOWING = 1L << 32;
// Touchpad gestures are disabled
public static final long SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED = 1L << 33;
+ // PiP animation is running
+ public static final long SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING = 1L << 34;
// Mask for SystemUiStateFlags to isolate SYSUI_STATE_AWAKE and
// SYSUI_STATE_WAKEFULNESS_TRANSITION, to match WAKEFULNESS_* constants
@@ -173,6 +175,7 @@
SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY,
SYSUI_STATE_SHORTCUT_HELPER_SHOWING,
SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED,
+ SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING,
})
public @interface SystemUiStateFlags {}
@@ -277,6 +280,9 @@
if ((flags & SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED) != 0) {
str.add("touchpad_gestures_disabled");
}
+ if ((flags & SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING) != 0) {
+ str.add("disable_gesture_pip_animating");
+ }
return str.toString();
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 9d573d3..4a28d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -469,6 +469,7 @@
private void onSpringAnimationsEndAction() {
if (mShouldShowDockTooltip) {
+ mEduTooltipView.ifPresent(this::removeTooltip);
mEduTooltipView = Optional.of(new MenuEduTooltipView(mContext, mMenuViewAppearance));
mEduTooltipView.ifPresent(view -> addTooltipView(view,
getContext().getText(R.string.accessibility_floating_button_docking_tooltip),
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
index f905241..636bc5b 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/BouncerSwipeTouchHandler.java
@@ -38,6 +38,7 @@
import com.android.systemui.ambient.touch.scrim.ScrimController;
import com.android.systemui.ambient.touch.scrim.ScrimManager;
import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants;
+import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shade.ShadeExpansionChangeEvent;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -103,6 +104,8 @@
private final UiEventLogger mUiEventLogger;
+ private final ActivityStarter mActivityStarter;
+
private final ScrimManager.Callback mScrimManagerCallback = new ScrimManager.Callback() {
@Override
public void onScrimControllerChanged(ScrimController controller) {
@@ -149,11 +152,16 @@
return true;
}
- // If scrolling up and keyguard is not locked, dismiss the dream since there's
- // no bouncer to show.
+ // If scrolling up and keyguard is not locked, dismiss both keyguard and the
+ // dream since there's no bouncer to show.
if (e1.getY() > e2.getY()
&& !mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
- mCentralSurfaces.get().awakenDreams();
+ mActivityStarter.executeRunnableDismissingKeyguard(
+ () -> mCentralSurfaces.get().awakenDreams(),
+ /* cancelAction= */ null,
+ /* dismissShade= */ true,
+ /* afterKeyguardGone= */ true,
+ /* deferred= */ false);
return true;
}
@@ -162,7 +170,6 @@
// bouncer. As that view's expansion shrinks, the bouncer appears. The bouncer
// is fully hidden at full expansion (1) and fully visible when fully collapsed
// (0).
- final float dragDownAmount = e2.getY() - e1.getY();
final float screenTravelPercentage = Math.abs(e1.getY() - e2.getY())
/ mTouchSession.getBounds().height();
setPanelExpansion(1 - screenTravelPercentage);
@@ -216,7 +223,8 @@
FlingAnimationUtils flingAnimationUtilsClosing,
@Named(BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage,
@Named(BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage,
- UiEventLogger uiEventLogger) {
+ UiEventLogger uiEventLogger,
+ ActivityStarter activityStarter) {
mCentralSurfaces = centralSurfaces;
mScrimManager = scrimManager;
mNotificationShadeWindowController = notificationShadeWindowController;
@@ -229,6 +237,7 @@
mValueAnimatorCreator = valueAnimatorCreator;
mVelocityTrackerFactory = velocityTrackerFactory;
mUiEventLogger = uiEventLogger;
+ mActivityStarter = activityStarter;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 01cc33c..e03d160 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -45,7 +45,6 @@
import androidx.annotation.LayoutRes
import androidx.annotation.VisibleForTesting
import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.systemui.Flags.udfpsViewPerformance
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
@@ -82,67 +81,66 @@
private const val TAG = "UdfpsControllerOverlay"
-@VisibleForTesting
-const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui"
+@VisibleForTesting const val SETTING_REMOVE_ENROLLMENT_UI = "udfps_overlay_remove_enrollment_ui"
/**
* Keeps track of the overlay state and UI resources associated with a single FingerprintService
- * request. This state can persist across configuration changes via the [show] and [hide]
- * methods.
+ * request. This state can persist across configuration changes via the [show] and [hide] methods.
*/
@ExperimentalCoroutinesApi
@UiThread
-class UdfpsControllerOverlay @JvmOverloads constructor(
- private val context: Context,
- private val inflater: LayoutInflater,
- private val windowManager: WindowManager,
- private val accessibilityManager: AccessibilityManager,
- private val statusBarStateController: StatusBarStateController,
- private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
- private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
- private val dialogManager: SystemUIDialogManager,
- private val dumpManager: DumpManager,
- private val transitionController: LockscreenShadeTransitionController,
- private val configurationController: ConfigurationController,
- private val keyguardStateController: KeyguardStateController,
- private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
- private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
- val requestId: Long,
- @RequestReason val requestReason: Int,
- private val controllerCallback: IUdfpsOverlayControllerCallback,
- private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
- private val activityTransitionAnimator: ActivityTransitionAnimator,
- private val primaryBouncerInteractor: PrimaryBouncerInteractor,
- private val alternateBouncerInteractor: AlternateBouncerInteractor,
- private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
- private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
- private val transitionInteractor: KeyguardTransitionInteractor,
- private val selectedUserInteractor: SelectedUserInteractor,
- private val deviceEntryUdfpsTouchOverlayViewModel:
- Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
- private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>,
- private val shadeInteractor: ShadeInteractor,
- private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
- private val powerInteractor: PowerInteractor,
- @Application private val scope: CoroutineScope,
+class UdfpsControllerOverlay
+@JvmOverloads
+constructor(
+ private val context: Context,
+ private val inflater: LayoutInflater,
+ private val windowManager: WindowManager,
+ private val accessibilityManager: AccessibilityManager,
+ private val statusBarStateController: StatusBarStateController,
+ private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val dialogManager: SystemUIDialogManager,
+ private val dumpManager: DumpManager,
+ private val transitionController: LockscreenShadeTransitionController,
+ private val configurationController: ConfigurationController,
+ private val keyguardStateController: KeyguardStateController,
+ private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+ private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+ val requestId: Long,
+ @RequestReason val requestReason: Int,
+ private val controllerCallback: IUdfpsOverlayControllerCallback,
+ private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
+ private val activityTransitionAnimator: ActivityTransitionAnimator,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val isDebuggable: Boolean = Build.IS_DEBUGGABLE,
+ private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
+ private val transitionInteractor: KeyguardTransitionInteractor,
+ private val selectedUserInteractor: SelectedUserInteractor,
+ private val deviceEntryUdfpsTouchOverlayViewModel: Lazy<DeviceEntryUdfpsTouchOverlayViewModel>,
+ private val defaultUdfpsTouchOverlayViewModel: Lazy<DefaultUdfpsTouchOverlayViewModel>,
+ private val shadeInteractor: ShadeInteractor,
+ private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
+ private val powerInteractor: PowerInteractor,
+ @Application private val scope: CoroutineScope,
) {
private val currentStateUpdatedToOffAodOrDozing: Flow<Unit> =
transitionInteractor.currentKeyguardState
.filter {
- it == KeyguardState.OFF ||
- it == KeyguardState.AOD ||
- it == KeyguardState.DOZING
+ it == KeyguardState.OFF || it == KeyguardState.AOD || it == KeyguardState.DOZING
}
- .map { } // map to Unit
+ .map {} // map to Unit
private var listenForCurrentKeyguardState: Job? = null
private var addViewRunnable: Runnable? = null
private var overlayViewLegacy: UdfpsView? = null
private set
+
private var overlayTouchView: UdfpsTouchOverlay? = null
/**
- * Get the current UDFPS overlay touch view which is a different View depending on whether
- * the DeviceEntryUdfpsRefactor flag is enabled or not.
+ * Get the current UDFPS overlay touch view which is a different View depending on whether the
+ * DeviceEntryUdfpsRefactor flag is enabled or not.
+ *
* @return The view, when [isShowing], else null
*/
fun getTouchOverlay(): View? {
@@ -158,23 +156,28 @@
private var overlayTouchListener: TouchExplorationStateChangeListener? = null
- private val coreLayoutParams = WindowManager.LayoutParams(
- WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
- 0 /* flags set in computeLayoutParams() */,
- PixelFormat.TRANSLUCENT
- ).apply {
- title = TAG
- fitInsetsTypes = 0
- gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
- layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
- flags = (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or
- WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
- privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or
- WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION
- // Avoid announcing window title.
- accessibilityTitle = " "
- inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
- }
+ private val coreLayoutParams =
+ WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+ 0 /* flags set in computeLayoutParams() */,
+ PixelFormat.TRANSLUCENT
+ )
+ .apply {
+ title = TAG
+ fitInsetsTypes = 0
+ gravity = android.view.Gravity.TOP or android.view.Gravity.LEFT
+ layoutInDisplayCutoutMode =
+ WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ flags =
+ (Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS or
+ WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
+ privateFlags =
+ WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY or
+ WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION
+ // Avoid announcing window title.
+ accessibilityTitle = " "
+ inputFeatures = WindowManager.LayoutParams.INPUT_FEATURE_SPY
+ }
/** If the overlay is currently showing. */
val isShowing: Boolean
@@ -209,51 +212,51 @@
sensorBounds = Rect(params.sensorBounds)
try {
if (DeviceEntryUdfpsRefactor.isEnabled) {
- overlayTouchView = (inflater.inflate(
- R.layout.udfps_touch_overlay, null, false
- ) as UdfpsTouchOverlay).apply {
- // This view overlaps the sensor area
- // prevent it from being selectable during a11y
- if (requestReason.isImportantForAccessibility()) {
- importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
- }
+ overlayTouchView =
+ (inflater.inflate(R.layout.udfps_touch_overlay, null, false)
+ as UdfpsTouchOverlay)
+ .apply {
+ // This view overlaps the sensor area
+ // prevent it from being selectable during a11y
+ if (requestReason.isImportantForAccessibility()) {
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ }
- addViewNowOrLater(this, null)
- when (requestReason) {
- REASON_AUTH_KEYGUARD ->
- UdfpsTouchOverlayBinder.bind(
- view = this,
- viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(),
- udfpsOverlayInteractor = udfpsOverlayInteractor,
- )
- else ->
- UdfpsTouchOverlayBinder.bind(
- view = this,
- viewModel = defaultUdfpsTouchOverlayViewModel.get(),
- udfpsOverlayInteractor = udfpsOverlayInteractor,
- )
- }
- }
+ addViewNowOrLater(this, null)
+ when (requestReason) {
+ REASON_AUTH_KEYGUARD ->
+ UdfpsTouchOverlayBinder.bind(
+ view = this,
+ viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(),
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
+ )
+ else ->
+ UdfpsTouchOverlayBinder.bind(
+ view = this,
+ viewModel = defaultUdfpsTouchOverlayViewModel.get(),
+ udfpsOverlayInteractor = udfpsOverlayInteractor,
+ )
+ }
+ }
} else {
- overlayViewLegacy = (inflater.inflate(
- R.layout.udfps_view, null, false
- ) as UdfpsView).apply {
- overlayParams = params
- setUdfpsDisplayModeProvider(udfpsDisplayModeProvider)
- val animation = inflateUdfpsAnimation(this, controller)
- if (animation != null) {
- animation.init()
- animationViewController = animation
- }
- // This view overlaps the sensor area
- // prevent it from being selectable during a11y
- if (requestReason.isImportantForAccessibility()) {
- importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
- }
+ overlayViewLegacy =
+ (inflater.inflate(R.layout.udfps_view, null, false) as UdfpsView).apply {
+ overlayParams = params
+ setUdfpsDisplayModeProvider(udfpsDisplayModeProvider)
+ val animation = inflateUdfpsAnimation(this, controller)
+ if (animation != null) {
+ animation.init()
+ animationViewController = animation
+ }
+ // This view overlaps the sensor area
+ // prevent it from being selectable during a11y
+ if (requestReason.isImportantForAccessibility()) {
+ importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ }
- addViewNowOrLater(this, animation)
- sensorRect = sensorBounds
- }
+ addViewNowOrLater(this, animation)
+ sensorRect = sensorBounds
+ }
}
getTouchOverlay()?.apply {
touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled
@@ -269,7 +272,7 @@
}
}
accessibilityManager.addTouchExplorationStateChangeListener(
- overlayTouchListener!!
+ overlayTouchListener!!
)
overlayTouchListener?.onTouchExplorationStateChanged(true)
}
@@ -284,30 +287,18 @@
}
private fun addViewNowOrLater(view: View, animation: UdfpsAnimationViewController<*>?) {
- if (udfpsViewPerformance()) {
- addViewRunnable = kotlinx.coroutines.Runnable {
+ addViewRunnable =
+ kotlinx.coroutines.Runnable {
Trace.setCounter("UdfpsAddView", 1)
- windowManager.addView(
- view,
- coreLayoutParams.updateDimensions(animation)
- )
+ windowManager.addView(view, coreLayoutParams.updateDimensions(animation))
}
- if (powerInteractor.detailedWakefulness.value.isAwake()) {
- // Device is awake, so we add the view immediately.
- addViewIfPending()
- } else {
- listenForCurrentKeyguardState?.cancel()
- listenForCurrentKeyguardState = scope.launch {
- currentStateUpdatedToOffAodOrDozing.collect {
- addViewIfPending()
- }
- }
- }
+ if (powerInteractor.detailedWakefulness.value.isAwake()) {
+ // Device is awake, so we add the view immediately.
+ addViewIfPending()
} else {
- windowManager.addView(
- view,
- coreLayoutParams.updateDimensions(animation)
- )
+ listenForCurrentKeyguardState?.cancel()
+ listenForCurrentKeyguardState =
+ scope.launch { currentStateUpdatedToOffAodOrDozing.collect { addViewIfPending() } }
}
}
@@ -340,23 +331,26 @@
): UdfpsAnimationViewController<*>? {
DeviceEntryUdfpsRefactor.assertInLegacyMode()
- val isEnrollment = when (requestReason) {
- REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true
- else -> false
- }
+ val isEnrollment =
+ when (requestReason) {
+ REASON_ENROLL_FIND_SENSOR,
+ REASON_ENROLL_ENROLLING -> true
+ else -> false
+ }
- val filteredRequestReason = if (isEnrollment && shouldRemoveEnrollmentUi()) {
- REASON_AUTH_OTHER
- } else {
- requestReason
- }
+ val filteredRequestReason =
+ if (isEnrollment && shouldRemoveEnrollmentUi()) {
+ REASON_AUTH_OTHER
+ } else {
+ requestReason
+ }
return when (filteredRequestReason) {
REASON_ENROLL_FIND_SENSOR,
REASON_ENROLL_ENROLLING -> {
// Enroll udfps UI is handled by settings, so use empty view here
UdfpsFpmEmptyViewController(
- view.addUdfpsView(R.layout.udfps_fpm_empty_view){
+ view.addUdfpsView(R.layout.udfps_fpm_empty_view) {
updateAccessibilityViewLocation(sensorBounds)
},
statusBarStateController,
@@ -434,14 +428,10 @@
udfpsDisplayModeProvider.disable(null)
}
getTouchOverlay()?.apply {
- if (udfpsViewPerformance()) {
- if (this.parent != null) {
- windowManager.removeView(this)
- }
- Trace.setCounter("UdfpsAddView", 0)
- } else {
+ if (this.parent != null) {
windowManager.removeView(this)
}
+ Trace.setCounter("UdfpsAddView", 0)
setOnTouchListener(null)
setOnHoverListener(null)
overlayTouchListener?.let {
@@ -475,22 +465,19 @@
val paddingX = animation?.paddingX ?: 0
val paddingY = animation?.paddingY ?: 0
- val isEnrollment = when (requestReason) {
- REASON_ENROLL_FIND_SENSOR, REASON_ENROLL_ENROLLING -> true
- else -> false
- }
+ val isEnrollment =
+ when (requestReason) {
+ REASON_ENROLL_FIND_SENSOR,
+ REASON_ENROLL_ENROLLING -> true
+ else -> false
+ }
// Use expanded overlay unless touchExploration enabled
var rotatedBounds =
if (accessibilityManager.isTouchExplorationEnabled && isEnrollment) {
Rect(overlayParams.sensorBounds)
} else {
- Rect(
- 0,
- 0,
- overlayParams.naturalDisplayWidth,
- overlayParams.naturalDisplayHeight
- )
+ Rect(0, 0, overlayParams.naturalDisplayWidth, overlayParams.naturalDisplayHeight)
}
val rot = overlayParams.rotation
@@ -498,7 +485,8 @@
if (!shouldRotate(animation)) {
Log.v(
TAG,
- "Skip rotating UDFPS bounds " + Surface.rotationToString(rot) +
+ "Skip rotating UDFPS bounds " +
+ Surface.rotationToString(rot) +
" animation=$animation" +
" isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" +
" isOccluded=${keyguardStateController.isOccluded}"
@@ -559,6 +547,4 @@
@RequestReason
private fun Int.isImportantForAccessibility() =
- this == REASON_ENROLL_FIND_SENSOR ||
- this == REASON_ENROLL_ENROLLING ||
- this == REASON_AUTH_BP
+ this == REASON_ENROLL_FIND_SENSOR || this == REASON_ENROLL_ENROLLING || this == REASON_AUTH_BP
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index 94f465d..eaddc42 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -288,11 +288,13 @@
private fun startSettingsActivity(intent: Intent, view: View) {
if (job?.isActive == true) {
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
- activityStarter.postStartActivityDismissingKeyguard(
- intent,
- 0,
- dialogTransitionAnimator.createActivityTransitionController(view)
- )
+ val controller = dialogTransitionAnimator.createActivityTransitionController(view)
+ // The controller will be null when the screen is locked and going to show the
+ // primary bouncer. In this case we dismiss the dialog manually.
+ if (controller == null) {
+ cancelJob()
+ }
+ activityStarter.postStartActivityDismissingKeyguard(intent, 0, controller)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt
index 5cd15f2..75f0bad 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/model/CommunalWidgetCategories.kt
@@ -17,7 +17,6 @@
package com.android.systemui.communal.data.model
import android.appwidget.AppWidgetProviderInfo
-import com.android.settingslib.flags.Flags.allowAllWidgetsOnLockscreenByDefault
/**
* The widget categories to display on communal hub (where categories is a bitfield with values that
@@ -31,9 +30,7 @@
val defaultCategories: Int
get() {
return AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD or
- if (allowAllWidgetsOnLockscreenByDefault())
- AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
- else 0
+ AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
index 1c47e50..2940a95 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt
@@ -24,7 +24,6 @@
import com.android.systemui.Flags.communalHub
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.communal.data.model.CommunalEnabledState
-import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.data.model.DisabledReason
import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_DEVICE_POLICY
import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_FLAG
@@ -52,12 +51,6 @@
/** A [CommunalEnabledState] for the specified user. */
fun getEnabledState(user: UserInfo): Flow<CommunalEnabledState>
- /**
- * A flow that reports the widget categories to show on the hub as selected by the user in
- * Settings.
- */
- fun getWidgetCategories(user: UserInfo): Flow<CommunalWidgetCategories>
-
/** Keyguard widgets enabled state by Device Policy Manager for the specified user. */
fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean>
@@ -104,22 +97,6 @@
.flowOn(bgDispatcher)
}
- override fun getWidgetCategories(user: UserInfo): Flow<CommunalWidgetCategories> =
- secureSettings
- .observerFlow(userId = user.id, names = arrayOf(GLANCEABLE_HUB_CONTENT_SETTING))
- // Force an update
- .onStart { emit(Unit) }
- .map {
- CommunalWidgetCategories(
- secureSettings.getIntForUser(
- GLANCEABLE_HUB_CONTENT_SETTING,
- CommunalWidgetCategories.defaultCategories,
- user.id
- )
- )
- }
- .flowOn(bgDispatcher)
-
override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> =
broadcastDispatcher
.broadcastFlow(
@@ -159,7 +136,6 @@
}
companion object {
- const val GLANCEABLE_HUB_CONTENT_SETTING = "glanceable_hub_content_setting"
const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background"
private const val ENABLED_SETTING_DEFAULT = 1
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 9f3ade9..f5255ac 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -392,26 +392,17 @@
allowedForWorkProfile ->
filterWidgetsAllowedByDevicePolicy(widgets, allowedForWorkProfile)
},
- communalSettingsInteractor.communalWidgetCategories,
updateOnWorkProfileBroadcastReceived,
- ) { widgets, allowedCategories, _ ->
+ ) { widgets, _ ->
widgets.map { widget ->
when (widget) {
is CommunalWidgetContentModel.Available -> {
- if (widget.providerInfo.widgetCategory and allowedCategories != 0) {
- // At least one category this widget specified is allowed, so show it
- WidgetContent.Widget(
- appWidgetId = widget.appWidgetId,
- providerInfo = widget.providerInfo,
- appWidgetHost = appWidgetHost,
- inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
- )
- } else {
- WidgetContent.DisabledWidget(
- appWidgetId = widget.appWidgetId,
- providerInfo = widget.providerInfo,
- )
- }
+ WidgetContent.Widget(
+ appWidgetId = widget.appWidgetId,
+ providerInfo = widget.providerInfo,
+ appWidgetHost = appWidgetHost,
+ inQuietMode = isQuietModeEnabled(widget.providerInfo.profile)
+ )
}
is CommunalWidgetContentModel.Pending -> {
WidgetContent.PendingWidget(
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
index f043d58..47b75c4 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt
@@ -19,7 +19,6 @@
import android.content.pm.UserInfo
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.communal.data.model.CommunalEnabledState
-import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.data.repository.CommunalSettingsRepository
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.dagger.SysUISingleton
@@ -70,18 +69,6 @@
// Start this eagerly since the value is accessed synchronously in many places.
.stateIn(scope = bgScope, started = SharingStarted.Eagerly, initialValue = false)
- /** What widget categories to show on the hub. */
- val communalWidgetCategories: StateFlow<Int> =
- userInteractor.selectedUserInfo
- .flatMapLatest { user -> repository.getWidgetCategories(user) }
- .map { categories -> categories.categories }
- .stateIn(
- scope = bgScope,
- // Start this eagerly since the value can be accessed synchronously.
- started = SharingStarted.Eagerly,
- initialValue = CommunalWidgetCategories.defaultCategories
- )
-
/** The type of background to use for the hub. Used to experiment with different backgrounds */
val communalBackground: Flow<CommunalBackgroundType> =
userInteractor.selectedUserInfo
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
index 9185384..fab2435 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalEditModeViewModel.kt
@@ -25,6 +25,7 @@
import androidx.activity.result.ActivityResultLauncher
import com.android.internal.logging.UiEventLogger
import com.android.systemui.Flags.enableWidgetPickerSizeFilter
+import com.android.systemui.communal.data.model.CommunalWidgetCategories
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.domain.interactor.CommunalPrefsInteractor
import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
@@ -183,7 +184,7 @@
}
putExtra(
AppWidgetManager.EXTRA_CATEGORY_FILTER,
- communalSettingsInteractor.communalWidgetCategories.value
+ CommunalWidgetCategories.defaultCategories
)
putExtra(EXTRA_UI_SURFACE_KEY, EXTRA_UI_SURFACE_VALUE)
putParcelableArrayListExtra(EXTRA_ADDED_APP_WIDGETS_KEY, excludeList)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 2043dd1..0466bbc 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -27,6 +27,7 @@
import com.android.systemui.communal.shared.model.CommunalBackgroundType
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -55,6 +56,8 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -64,6 +67,7 @@
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/** The default view model used for showing the communal hub. */
@@ -74,6 +78,7 @@
constructor(
@Main val mainDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
+ @Background private val bgScope: CoroutineScope,
@Main private val resources: Resources,
keyguardTransitionInteractor: KeyguardTransitionInteractor,
keyguardInteractor: KeyguardInteractor,
@@ -303,8 +308,12 @@
*
* This is needed because the notification shade does not block touches in blank areas and these
* fall through to the glanceable hub, which we don't want.
+ *
+ * Using a StateFlow as the value does not necessarily change when hub becomes available.
*/
- val touchesAllowed: Flow<Boolean> = not(shadeInteractor.isAnyFullyExpanded)
+ val touchesAllowed: StateFlow<Boolean> =
+ not(shadeInteractor.isAnyFullyExpanded)
+ .stateIn(bgScope, SharingStarted.Eagerly, initialValue = false)
// TODO(b/339667383): remove this temporary swipe gesture handle
/**
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
index 76be005..af87f09 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
@@ -22,6 +22,7 @@
import com.android.systemui.communal.widgets.EditWidgetsActivity.Companion.EXTRA_PRESELECTED_KEY
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.res.R
import javax.inject.Inject
interface EditWidgetsActivityStarter {
@@ -48,6 +49,7 @@
},
/* onlyProvisioned = */ true,
/* dismissShade = */ true,
+ applicationContext.resources.getString(R.string.unlock_reason_to_customize_widgets),
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index cbc6c97..72f9180 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -34,10 +34,11 @@
private val activityStarter: ActivityStarter,
) : RemoteViews.InteractionHandler {
- private val delegate = InteractionHandlerDelegate(
- findViewToAnimate = { view -> view is CommunalAppWidgetHostView },
- intentStarter = this::startIntent,
- )
+ private val delegate =
+ InteractionHandlerDelegate(
+ findViewToAnimate = { view -> view is CommunalAppWidgetHostView },
+ intentStarter = this::startIntent,
+ )
override fun onInteraction(
view: View,
@@ -45,7 +46,6 @@
response: RemoteViews.RemoteResponse
): Boolean = delegate.onInteraction(view, pendingIntent, response)
-
private fun startIntent(
pendingIntent: PendingIntent,
fillInIntent: Intent,
@@ -59,6 +59,8 @@
controller,
fillInIntent,
extraOptions.toBundle(),
+ // TODO(b/325110448): UX to provide copy
+ /* customMessage = */ null,
)
return true
}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
index 92108e9..afa2375 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
@@ -18,7 +18,6 @@
import static com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent.DreamHomeControlsModule.DREAM_HOME_CONTROLS_CHIP_VIEW;
import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS;
-import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE_AFTER_UNLOCK;
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.UNAVAILABLE;
@@ -90,7 +89,6 @@
private final DreamHomeControlsComplication mComplication;
private final DreamOverlayStateController mDreamOverlayStateController;
private final ControlsComponent mControlsComponent;
- private final boolean mReplacedByOpenHub;
private boolean mOverlayActive = false;
@@ -118,13 +116,11 @@
public Registrant(DreamHomeControlsComplication complication,
DreamOverlayStateController dreamOverlayStateController,
ControlsComponent controlsComponent,
- @SystemUser Monitor monitor,
- @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) boolean replacedByOpenHub) {
+ @SystemUser Monitor monitor) {
super(monitor);
mComplication = complication;
mControlsComponent = controlsComponent;
mDreamOverlayStateController = dreamOverlayStateController;
- mReplacedByOpenHub = replacedByOpenHub;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java
deleted file mode 100644
index 05df2bb..0000000
--- a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java
+++ /dev/null
@@ -1,211 +0,0 @@
-/*
- * 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.complication;
-
-import static com.android.systemui.complication.dagger.OpenHubComplicationComponent.OpenHubModule.OPEN_HUB_CHIP_VIEW;
-import static com.android.systemui.complication.dagger.RegisteredComplicationsModule.OPEN_HUB_CHIP_LAYOUT_PARAMS;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.util.Log;
-import android.view.View;
-import android.widget.ImageView;
-
-import com.android.settingslib.Utils;
-import com.android.systemui.CoreStartable;
-import com.android.systemui.communal.domain.interactor.CommunalInteractor;
-import com.android.systemui.communal.shared.model.CommunalScenes;
-import com.android.systemui.complication.dagger.OpenHubComplicationComponent;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dagger.qualifiers.SystemUser;
-import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.shared.condition.Monitor;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.util.ViewController;
-import com.android.systemui.util.condition.ConditionalCoreStartable;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * A dream complication that shows a chip to open the glanceable hub.
- */
-// TODO(b/339667383): delete or properly implement this once a product decision is made
-public class OpenHubComplication implements Complication {
- private final Resources mResources;
- private final OpenHubComplicationComponent.Factory mComponentFactory;
-
- @Inject
- public OpenHubComplication(
- @Main Resources resources,
- OpenHubComplicationComponent.Factory componentFactory) {
- mResources = resources;
- mComponentFactory = componentFactory;
- }
-
- @Override
- public ViewHolder createView(ComplicationViewModel model) {
- return mComponentFactory.create(mResources).getViewHolder();
- }
-
- @Override
- public int getRequiredTypeAvailability() {
- // TODO(b/339667383): create a new complication type if we decide to productionize this
- return COMPLICATION_TYPE_NONE;
- }
-
- /**
- * {@link CoreStartable} for registering the complication with SystemUI on startup.
- */
- public static class Registrant extends ConditionalCoreStartable {
- private final OpenHubComplication mComplication;
- private final DreamOverlayStateController mDreamOverlayStateController;
-
- private boolean mOverlayActive = false;
-
- private final DreamOverlayStateController.Callback mOverlayStateCallback =
- new DreamOverlayStateController.Callback() {
- @Override
- public void onStateChanged() {
- if (mOverlayActive == mDreamOverlayStateController.isOverlayActive()) {
- return;
- }
-
- mOverlayActive = !mOverlayActive;
-
- if (mOverlayActive) {
- updateOpenHubComplication();
- }
- }
- };
-
- @Inject
- public Registrant(OpenHubComplication complication,
- DreamOverlayStateController dreamOverlayStateController,
- @SystemUser Monitor monitor) {
- super(monitor);
- mComplication = complication;
- mDreamOverlayStateController = dreamOverlayStateController;
- }
-
- @Override
- public void onStart() {
- mDreamOverlayStateController.addCallback(mOverlayStateCallback);
- }
-
- private void updateOpenHubComplication() {
- // TODO(b/339667383): don't show the complication if glanceable hub is disabled
-// if (Flags.glanceableHubShortcutButton()) {
-// mDreamOverlayStateController.addComplication(mComplication);
-// } else {
-// mDreamOverlayStateController.removeComplication(mComplication);
-// }
- }
- }
-
- /**
- * Contains values/logic associated with the dream complication view.
- */
- public static class OpenHubChipViewHolder implements ViewHolder {
- private final ImageView mView;
- private final ComplicationLayoutParams mLayoutParams;
- private final OpenHubChipViewController mViewController;
-
- @Inject
- OpenHubChipViewHolder(
- OpenHubChipViewController dreamOpenHubChipViewController,
- @Named(OPEN_HUB_CHIP_VIEW) ImageView view,
- @Named(OPEN_HUB_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams
- ) {
- mView = view;
- mLayoutParams = layoutParams;
- mViewController = dreamOpenHubChipViewController;
- mViewController.init();
- }
-
- @Override
- public ImageView getView() {
- return mView;
- }
-
- @Override
- public ComplicationLayoutParams getLayoutParams() {
- return mLayoutParams;
- }
- }
-
- /**
- * Controls behavior of the dream complication.
- */
- static class OpenHubChipViewController extends ViewController<ImageView> {
- private static final String TAG = "OpenHubCtrl";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private final Context mContext;
- private final ConfigurationController mConfigurationController;
-
- private final ConfigurationController.ConfigurationListener mConfigurationListener =
- new ConfigurationController.ConfigurationListener() {
- @Override
- public void onUiModeChanged() {
- reloadResources();
- }
- };
- private final CommunalInteractor mCommunalInteractor;
-
- @Inject
- OpenHubChipViewController(
- @Named(OPEN_HUB_CHIP_VIEW) ImageView view,
- Context context,
- ConfigurationController configurationController,
- CommunalInteractor communalInteractor) {
- super(view);
-
- mContext = context;
- mConfigurationController = configurationController;
- mCommunalInteractor = communalInteractor;
- }
-
- @Override
- protected void onViewAttached() {
- reloadResources();
- mView.setOnClickListener(this::onClickOpenHub);
- mConfigurationController.addCallback(mConfigurationListener);
- }
-
- @Override
- protected void onViewDetached() {
- mConfigurationController.removeCallback(mConfigurationListener);
- }
-
- private void reloadResources() {
- mView.setImageTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary));
- final Drawable background = mView.getBackground();
- if (background != null) {
- background.setTintList(
- Utils.getColorAttr(mContext, com.android.internal.R.attr.colorSurface));
- }
- }
-
- private void onClickOpenHub(View v) {
- if (DEBUG) Log.d(TAG, "open hub complication tapped");
-
- mCommunalInteractor.changeScene(CommunalScenes.Communal, null);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java
deleted file mode 100644
index 501601e..0000000
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/OpenHubComplicationComponent.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * 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.complication.dagger;
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import android.content.Context;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.view.LayoutInflater;
-import android.widget.ImageView;
-
-import com.android.systemui.complication.OpenHubComplication;
-import com.android.systemui.res.R;
-import com.android.systemui.shared.shadow.DoubleShadowIconDrawable;
-import com.android.systemui.shared.shadow.DoubleShadowTextHelper;
-
-import dagger.BindsInstance;
-import dagger.Module;
-import dagger.Provides;
-import dagger.Subcomponent;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Named;
-import javax.inject.Scope;
-
-/**
- * Responsible for generating dependencies for the {@link OpenHubComplication}.
- */
-@Subcomponent(modules = OpenHubComplicationComponent.OpenHubModule.class)
-@OpenHubComplicationComponent.OpenHubComplicationScope
-public interface OpenHubComplicationComponent {
- /**
- * Creates a view holder for the open hub complication.
- */
- OpenHubComplication.OpenHubChipViewHolder getViewHolder();
-
- /**
- * Scope of the open hub complication.
- */
- @Documented
- @Retention(RUNTIME)
- @Scope
- @interface OpenHubComplicationScope {
- }
-
- /**
- * Factory that generates a {@link OpenHubComplicationComponent}.
- */
- @Subcomponent.Factory
- interface Factory {
- /**
- * Creates an instance of {@link OpenHubComplicationComponent}.
- */
- OpenHubComplicationComponent create(@BindsInstance Resources resources);
- }
-
- /**
- * Scoped injected values for the {@link OpenHubComplicationComponent}.
- */
- @Module
- interface OpenHubModule {
- String OPEN_HUB_CHIP_VIEW = "open_hub_chip_view";
- String OPEN_HUB_BACKGROUND_DRAWABLE = "open_hub_background_drawable";
-
- /**
- * Provides the dream open hub chip view.
- */
- @Provides
- @OpenHubComplicationScope
- @Named(OPEN_HUB_CHIP_VIEW)
- static ImageView provideOpenHubChipView(
- LayoutInflater layoutInflater,
- @Named(OPEN_HUB_BACKGROUND_DRAWABLE) Drawable backgroundDrawable) {
- final ImageView chip =
- (ImageView) layoutInflater.inflate(R.layout.dream_overlay_open_hub_chip,
- null, false);
- chip.setBackground(backgroundDrawable);
-
- return chip;
- }
-
- /**
- * Provides the background drawable for the open hub chip.
- */
- @Provides
- @OpenHubComplicationScope
- @Named(OPEN_HUB_BACKGROUND_DRAWABLE)
- static Drawable providesOpenHubBackground(Context context, Resources resources) {
- return new DoubleShadowIconDrawable(createShadowInfo(
- resources,
- R.dimen.dream_overlay_bottom_affordance_key_text_shadow_radius,
- R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dx,
- R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dy,
- R.dimen.dream_overlay_bottom_affordance_key_shadow_alpha
- ),
- createShadowInfo(
- resources,
- R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_radius,
- R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_dx,
- R.dimen.dream_overlay_bottom_affordance_ambient_text_shadow_dy,
- R.dimen.dream_overlay_bottom_affordance_ambient_shadow_alpha
- ),
- resources.getDrawable(R.drawable.dream_overlay_bottom_affordance_bg),
- resources.getDimensionPixelOffset(
- R.dimen.dream_overlay_bottom_affordance_width),
- resources.getDimensionPixelSize(R.dimen.dream_overlay_bottom_affordance_inset)
- );
- }
-
- private static DoubleShadowTextHelper.ShadowInfo createShadowInfo(Resources resources,
- int blurId, int offsetXId, int offsetYId, int alphaId) {
-
- return new DoubleShadowTextHelper.ShadowInfo(
- resources.getDimension(blurId),
- resources.getDimension(offsetXId),
- resources.getDimension(offsetYId),
- resources.getFloat(alphaId)
- );
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java
index edb5ff7..6f1b098 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/RegisteredComplicationsModule.java
@@ -25,7 +25,6 @@
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.res.R;
-import com.android.systemui.util.settings.SystemSettings;
import dagger.Module;
import dagger.Provides;
@@ -40,7 +39,6 @@
subcomponents = {
DreamClockTimeComplicationComponent.class,
DreamHomeControlsComplicationComponent.class,
- OpenHubComplicationComponent.class,
DreamMediaEntryComplicationComponent.class
})
public interface RegisteredComplicationsModule {
@@ -48,8 +46,6 @@
String DREAM_SMARTSPACE_LAYOUT_PARAMS = "smartspace_layout_params";
String DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS = "home_controls_chip_layout_params";
String DREAM_MEDIA_ENTRY_LAYOUT_PARAMS = "media_entry_layout_params";
- String OPEN_HUB_CHIP_LAYOUT_PARAMS = "open_hub_chip_layout_params";
- String OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS = "open_hub_chip_replace_home_controls";
int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT = 1;
int DREAM_CLOCK_TIME_COMPLICATION_WEIGHT_NO_SMARTSPACE = 2;
@@ -113,26 +109,6 @@
}
/**
- * Provides layout parameters for the open hub complication.
- */
- @Provides
- @Named(OPEN_HUB_CHIP_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideOpenHubLayoutParams(
- @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS) boolean replaceHomeControls) {
- int position = ComplicationLayoutParams.POSITION_BOTTOM | (replaceHomeControls
- ? ComplicationLayoutParams.POSITION_START
- : ComplicationLayoutParams.POSITION_END);
- int direction = replaceHomeControls ? ComplicationLayoutParams.DIRECTION_END
- : ComplicationLayoutParams.DIRECTION_START;
- return new ComplicationLayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- position,
- direction,
- DREAM_HOME_CONTROLS_CHIP_COMPLICATION_WEIGHT);
- }
-
- /**
* Provides layout parameters for the smartspace complication.
*/
@Provides
@@ -148,14 +124,4 @@
res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_padding),
res.getDimensionPixelSize(R.dimen.dream_overlay_complication_smartspace_max_width));
}
-
- /**
- * If true, the home controls chip should not be shown and the open hub chip should be shown in
- * its place.
- */
- @Provides
- @Named(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS)
- static boolean providesOpenHubChipReplaceHomeControls(SystemSettings systemSettings) {
- return systemSettings.getBool(OPEN_HUB_CHIP_REPLACE_HOME_CONTROLS, false);
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 1e4fb4f..c1de381 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -51,10 +51,14 @@
import android.database.ContentObserver;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.Flags;
import android.media.AudioManager;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
@@ -857,6 +861,29 @@
if (ActivityManager.isUserAMonkey()) {
return;
}
+ if (Flags.mandatoryBiometrics()
+ && requestBiometricAuthenticationForMandatoryBiometrics()) {
+ launchBiometricPromptForMandatoryBiometrics(
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationError(int errorCode,
+ CharSequence errString) {
+ super.onAuthenticationError(errorCode, errString);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded(
+ BiometricPrompt.AuthenticationResult result) {
+ super.onAuthenticationSucceeded(result);
+ shutDown();
+ }
+ });
+ } else {
+ shutDown();
+ }
+ }
+
+ private void shutDown() {
mUiEventLogger.log(GlobalActionsEvent.GA_SHUTDOWN_PRESS);
// shutdown by making sure radio and power are handled accordingly.
mWindowManagerFuncs.shutdown();
@@ -2261,6 +2288,35 @@
}
@VisibleForTesting
+ void launchBiometricPromptForMandatoryBiometrics(
+ BiometricPrompt.AuthenticationCallback authenticationCallback) {
+ final CancellationSignal cancellationSignal = new CancellationSignal();
+ final BiometricPrompt biometricPrompt = new BiometricPrompt.Builder(mContext)
+ .setAllowedAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS)
+ .setUseDefaultTitle()
+ .setDescription(mContext.getString(
+ R.string.identity_check_biometric_prompt_description))
+ .setNegativeButton(mContext.getString(R.string.cancel), mContext.getMainExecutor(),
+ (dialog, which) -> cancellationSignal.cancel())
+ .setAllowBackgroundAuthentication(true)
+ .build();
+ biometricPrompt.authenticate(cancellationSignal, mContext.getMainExecutor(),
+ authenticationCallback);
+ }
+
+ private boolean requestBiometricAuthenticationForMandatoryBiometrics() {
+ final BiometricManager biometricManager =
+ (BiometricManager) mContext.getSystemService(Context.BIOMETRIC_SERVICE);
+ if (biometricManager == null) {
+ Log.e(TAG, "Biometric Manager is null.");
+ return false;
+ }
+ final int status = biometricManager.canAuthenticate(
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+ return status == BiometricManager.BIOMETRIC_SUCCESS;
+ }
+
+ @VisibleForTesting
static class ActionsDialogLite extends SystemUIDialog implements DialogInterface,
ColorExtractor.OnColorsChangedListener {
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index 7b5139a..6cbe29e 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -160,7 +160,7 @@
}
fun onTileClick(): Boolean {
- if (state == State.TIMEOUT_WAIT) {
+ if (state == State.TIMEOUT_WAIT || state == State.IDLE) {
setState(State.IDLE)
qsTile?.let {
it.click(expandable)
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
index 04bde26..c00bd6f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepository.kt
@@ -16,11 +16,23 @@
package com.android.systemui.keyboard.shortcut.data.repository
+import android.view.KeyEvent
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
+import android.view.WindowManager
+import android.view.WindowManager.KeyboardShortcutsReceiver
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyboard.shortcut.data.source.MultitaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.data.source.SystemShortcutsSource
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState.Active
+import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory
import javax.inject.Inject
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.suspendCancellableCoroutine
@SysUISingleton
class ShortcutHelperCategoriesRepository
@@ -28,11 +40,77 @@
constructor(
private val systemShortcutsSource: SystemShortcutsSource,
private val multitaskingShortcutsSource: MultitaskingShortcutsSource,
+ private val windowManager: WindowManager,
+ shortcutHelperStateRepository: ShortcutHelperStateRepository
) {
- fun systemShortcutsCategory(): ShortcutCategory =
- systemShortcutsSource.systemShortcutsCategory()
+ val systemShortcutsCategory =
+ shortcutHelperStateRepository.state.map {
+ if (it is Active) systemShortcutsSource.systemShortcutsCategory() else null
+ }
- fun multitaskingShortcutsCategory(): ShortcutCategory =
- multitaskingShortcutsSource.multitaskingShortcutCategory()
+ val multitaskingShortcutsCategory =
+ shortcutHelperStateRepository.state.map {
+ if (it is Active) multitaskingShortcutsSource.multitaskingShortcutCategory() else null
+ }
+
+ val imeShortcutsCategory =
+ shortcutHelperStateRepository.state.map {
+ if (it is Active) retrieveImeShortcuts(it.deviceId) else null
+ }
+
+ private suspend fun retrieveImeShortcuts(deviceId: Int): ShortcutCategory {
+ return suspendCancellableCoroutine { continuation ->
+ val shortcutsReceiver = KeyboardShortcutsReceiver { shortcutGroups ->
+ continuation.resumeWith(Result.success(toShortcutCategory(shortcutGroups)))
+ }
+ windowManager.requestImeKeyboardShortcuts(shortcutsReceiver, deviceId)
+ }
+ }
+
+ private fun toShortcutCategory(shortcutGroups: List<KeyboardShortcutGroup>) =
+ shortcutCategory(ShortcutCategoryType.IME) {
+ shortcutGroups.map { shortcutGroup ->
+ subCategory(shortcutGroup.label.toString(), toShortcuts(shortcutGroup.items))
+ }
+ }
+
+ private fun toShortcuts(infoList: List<KeyboardShortcutInfo>) =
+ infoList.mapNotNull { toShortcut(it) }
+
+ private fun toShortcut(shortcutInfo: KeyboardShortcutInfo): Shortcut? {
+ val shortcutCommand = toShortcutCommand(shortcutInfo)
+ return if (shortcutCommand == null) null
+ else Shortcut(label = shortcutInfo.label!!.toString(), commands = listOf(shortcutCommand))
+ }
+
+ private fun toShortcutCommand(info: KeyboardShortcutInfo): ShortcutCommand? {
+ val keyCodes = mutableListOf<Int>()
+ var remainingModifiers = info.modifiers
+ SUPPORTED_MODIFIERS.forEach { supportedModifier ->
+ if ((supportedModifier and remainingModifiers) != 0) {
+ keyCodes += supportedModifier
+ // "Remove" the modifier from the remaining modifiers
+ remainingModifiers = remainingModifiers and supportedModifier.inv()
+ }
+ }
+ if (remainingModifiers != 0) {
+ // There is a remaining modifier we don't support
+ return null
+ }
+ keyCodes += info.keycode
+ return ShortcutCommand(keyCodes)
+ }
+
+ companion object {
+ private val SUPPORTED_MODIFIERS =
+ listOf(
+ KeyEvent.META_META_ON,
+ KeyEvent.META_CTRL_ON,
+ KeyEvent.META_ALT_ON,
+ KeyEvent.META_SHIFT_ON,
+ KeyEvent.META_SYM_ON,
+ KeyEvent.META_FUNCTION_ON
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
index 883407c..57d4b4a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractor.kt
@@ -18,30 +18,56 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCategoriesRepository
-import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
-import com.android.systemui.keyboard.shortcut.shared.model.ShortcutHelperState
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
@SysUISingleton
class ShortcutHelperCategoriesInteractor
@Inject
constructor(
- stateRepository: ShortcutHelperStateRepository,
categoriesRepository: ShortcutHelperCategoriesRepository,
) {
+ private val systemsShortcutCategory = categoriesRepository.systemShortcutsCategory
+ private val multitaskingShortcutsCategory = categoriesRepository.multitaskingShortcutsCategory
+ private val imeShortcutsCategory =
+ categoriesRepository.imeShortcutsCategory.map { groupSubCategoriesInCategory(it) }
+
val shortcutCategories: Flow<List<ShortcutCategory>> =
- stateRepository.state.map { state ->
- when (state) {
- is ShortcutHelperState.Active ->
- listOf(
- categoriesRepository.systemShortcutsCategory(),
- categoriesRepository.multitaskingShortcutsCategory()
- )
- is ShortcutHelperState.Inactive -> emptyList()
- }
+ combine(systemsShortcutCategory, multitaskingShortcutsCategory, imeShortcutsCategory) {
+ shortcutCategories ->
+ shortcutCategories.filterNotNull()
}
+
+ private fun groupSubCategoriesInCategory(
+ shortcutCategory: ShortcutCategory?
+ ): ShortcutCategory? {
+ if (shortcutCategory == null) {
+ return null
+ }
+ val subCategoriesWithGroupedShortcuts =
+ shortcutCategory.subCategories.map {
+ ShortcutSubCategory(
+ label = it.label,
+ shortcuts = groupShortcutsInSubcategory(it.shortcuts)
+ )
+ }
+ return ShortcutCategory(
+ type = shortcutCategory.type,
+ subCategories = subCategoriesWithGroupedShortcuts
+ )
+ }
+
+ private fun groupShortcutsInSubcategory(shortcuts: List<Shortcut>) =
+ shortcuts
+ .groupBy { it.label }
+ .entries
+ .map { (commonLabel, groupedShortcuts) ->
+ Shortcut(label = commonLabel, commands = groupedShortcuts.flatMap { it.commands })
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperStateInteractor.kt
index 3d707f7..299628e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperStateInteractor.kt
@@ -26,6 +26,7 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
@SysUISingleton
@@ -38,7 +39,7 @@
private val repository: ShortcutHelperStateRepository
) {
- val state: Flow<ShortcutHelperState> = repository.state
+ val state: Flow<ShortcutHelperState> = repository.state.asStateFlow()
fun onViewClosed() {
repository.hide()
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
index c5e8d2c..3ac7fa8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/shared/model/ShortcutCategory.kt
@@ -19,6 +19,7 @@
enum class ShortcutCategoryType {
SYSTEM,
MULTI_TASKING,
+ IME
}
data class ShortcutCategory(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index a7e2633..5f6fe1c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -105,6 +105,13 @@
const val UNLOCK_ANIMATION_DURATION_MS = 167L
/**
+ * If there are two different wallpapers on home and lock screen, duration and delay of the lock
+ * wallpaper fade out.
+ */
+const val LOCK_WALLPAPER_FADE_OUT_DURATION = 140L
+const val LOCK_WALLPAPER_FADE_OUT_START_DELAY = 0L
+
+/**
* How long the in-window launcher icon animation takes. This is used if the launcher is underneath
* the lock screen and supports in-window animations.
*
@@ -115,23 +122,24 @@
/**
* How long to wait for the shade to get out of the way before starting the canned unlock animation.
+ * If there are two different wallpapers on home and lock screen, this is also the duration and
+ * delay of the home wallpaper fade in.
*/
const val LEGACY_CANNED_UNLOCK_START_DELAY = 100L
-const val CANNED_UNLOCK_START_DELAY = 67L
+const val CANNED_UNLOCK_START_DELAY = 25L
/**
* Duration for the alpha animation on the surface behind. This plays to fade in the surface during
* a swipe to unlock (and to fade it back out if the swipe is cancelled).
*/
-const val LEGACY_SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 175L
-const val SURFACE_BEHIND_FADE_OUT_DURATION_MS = 83L
+const val SURFACE_BEHIND_SWIPE_FADE_DURATION_MS = 175L
/**
* Start delay for the surface behind animation, used so that the lockscreen can get out of the way
* before the surface begins appearing.
*/
const val LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS = 75L
-const val SURFACE_BEHIND_FADE_OUT_START_DELAY_MS = 0L
+const val UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS = 67L
/**
* Initiates, controls, and ends the keyguard unlock animation.
@@ -268,7 +276,8 @@
@VisibleForTesting
var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier? = null
private var surfaceBehindRemoteAnimationTargets: Array<RemoteAnimationTarget>? = null
- private var wallpaperTargets: Array<RemoteAnimationTarget>? = null
+ private var openingWallpaperTargets: Array<RemoteAnimationTarget>? = null
+ private var closingWallpaperTargets: Array<RemoteAnimationTarget>? = null
private var surfaceBehindRemoteAnimationStartTime: Long = 0
/**
@@ -286,6 +295,8 @@
var wallpaperCannedUnlockAnimator = ValueAnimator.ofFloat(0f, 1f)
+ var wallpaperFadeOutUnlockAnimator = ValueAnimator.ofFloat(1f, 0f)
+
/**
* Matrix applied to [surfaceBehindRemoteAnimationTarget], which is the surface of the
* app/launcher behind the keyguard.
@@ -335,7 +346,7 @@
init {
with(surfaceBehindAlphaAnimator) {
- duration = surfaceBehindFadeOutDurationMs()
+ duration = SURFACE_BEHIND_SWIPE_FADE_DURATION_MS
interpolator = Interpolators.LINEAR
addUpdateListener { valueAnimator: ValueAnimator ->
surfaceBehindAlpha = valueAnimator.animatedValue as Float
@@ -351,7 +362,8 @@
if (surfaceBehindAlpha == 0f) {
Log.d(TAG, "surfaceBehindAlphaAnimator#onAnimationEnd")
surfaceBehindRemoteAnimationTargets = null
- wallpaperTargets = null
+ openingWallpaperTargets = null
+ closingWallpaperTargets = null
keyguardViewMediator.get().finishSurfaceBehindRemoteAnimation(
false /* cancelled */)
} else {
@@ -367,8 +379,10 @@
else LAUNCHER_ICONS_ANIMATION_DURATION_MS
interpolator = if (fasterUnlockTransition()) Interpolators.LINEAR
else Interpolators.ALPHA_OUT
+ if (fasterUnlockTransition()) startDelay = CANNED_UNLOCK_START_DELAY
addUpdateListener { valueAnimator: ValueAnimator ->
- setWallpaperAppearAmount(valueAnimator.animatedValue as Float)
+ setWallpaperAppearAmount(
+ valueAnimator.animatedValue as Float, openingWallpaperTargets)
}
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
@@ -379,6 +393,18 @@
})
}
+ if (fasterUnlockTransition()) {
+ with(wallpaperFadeOutUnlockAnimator) {
+ duration = LOCK_WALLPAPER_FADE_OUT_DURATION
+ startDelay = LOCK_WALLPAPER_FADE_OUT_START_DELAY
+ interpolator = Interpolators.LINEAR
+ addUpdateListener { valueAnimator: ValueAnimator ->
+ setWallpaperAppearAmount(
+ valueAnimator.animatedValue as Float, closingWallpaperTargets)
+ }
+ }
+ }
+
with(surfaceBehindEntryAnimator) {
duration = unlockAnimationDurationMs()
startDelay = surfaceBehindFadeOutStartDelayMs()
@@ -546,7 +572,8 @@
*/
fun notifyStartSurfaceBehindRemoteAnimation(
targets: Array<RemoteAnimationTarget>,
- wallpapers: Array<RemoteAnimationTarget>,
+ openingWallpapers: Array<RemoteAnimationTarget>,
+ closingWallpapers: Array<RemoteAnimationTarget>,
startTime: Long,
requestedShowSurfaceBehindKeyguard: Boolean
) {
@@ -556,7 +583,8 @@
}
surfaceBehindRemoteAnimationTargets = targets
- wallpaperTargets = wallpapers
+ openingWallpaperTargets = openingWallpapers
+ closingWallpaperTargets = closingWallpapers
surfaceBehindRemoteAnimationStartTime = startTime
// If we specifically requested that the surface behind be made visible (vs. it being made
@@ -720,8 +748,9 @@
return@postDelayed
}
- if ((wallpaperTargets?.isNotEmpty() == true)) {
+ if ((openingWallpaperTargets?.isNotEmpty() == true)) {
fadeInWallpaper()
+ if (fasterUnlockTransition()) fadeOutWallpaper()
hideKeyguardViewAfterRemoteAnimation()
} else {
keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
@@ -855,7 +884,8 @@
/**
* Scales in and translates up the surface behind the keyguard. This is used during unlock
* animations and swipe gestures to animate the surface's entry (and exit, if the swipe is
- * cancelled).
+ * cancelled). When called with [wallpapers]=true, if there are different home and lock screen
+ * wallpapers, this transitions between the two wallpapers
*/
fun setSurfaceBehindAppearAmount(amount: Float, wallpapers: Boolean = true) {
val animationAlpha = when {
@@ -923,13 +953,27 @@
}
if (wallpapers) {
- setWallpaperAppearAmount(amount)
+ if (!fasterUnlockTransition()) setWallpaperAppearAmount(amount, openingWallpaperTargets)
+ else {
+ // Use the amount to compute the fadeInAmount and fadeOutAmount of the home and lock
+ // screen wallpapers to manually imitate the canned unlock animation.
+ val total = (UNLOCK_ANIMATION_DURATION_MS + CANNED_UNLOCK_START_DELAY).toFloat()
+ val fadeInStart = CANNED_UNLOCK_START_DELAY / total
+ val fadeInAmount = maxOf(0f, (amount - fadeInStart) / (1f - fadeInStart))
+
+ val fadeOutStart = LOCK_WALLPAPER_FADE_OUT_START_DELAY / total
+ val fadeOutEnd = fadeOutStart + LOCK_WALLPAPER_FADE_OUT_DURATION / total
+ val fadeOutAmount = ((amount - fadeOutStart) / (fadeOutEnd - fadeOutStart))
+ .coerceIn(0f, 1f)
+
+ setWallpaperAppearAmount(fadeInAmount, openingWallpaperTargets)
+ setWallpaperAppearAmount(1 - fadeOutAmount, closingWallpaperTargets)
+ }
}
}
- fun setWallpaperAppearAmount(amount: Float) {
+ fun setWallpaperAppearAmount(amount: Float, wallpaperTargets: Array<RemoteAnimationTarget>?) {
val animationAlpha = amount
-
wallpaperTargets?.forEach { wallpaper ->
// SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is
// unable to draw
@@ -991,7 +1035,8 @@
// That target is no longer valid since the animation finished, null it out.
surfaceBehindRemoteAnimationTargets = null
- wallpaperTargets = null
+ openingWallpaperTargets = null
+ if (fasterUnlockTransition()) closingWallpaperTargets = null
playingCannedUnlockAnimation = false
dismissAmountThresholdsReached = false
@@ -1035,6 +1080,12 @@
wallpaperCannedUnlockAnimator.start()
}
+ private fun fadeOutWallpaper() {
+ Log.d(TAG, "fadeOutWallpaper")
+ wallpaperFadeOutUnlockAnimator.cancel()
+ wallpaperFadeOutUnlockAnimator.start()
+ }
+
private fun fadeOutSurfaceBehind() {
Log.d(TAG, "fadeOutSurfaceBehind")
surfaceBehindAlphaAnimator.cancel()
@@ -1165,17 +1216,8 @@
* Temporary method for b/298186160
* TODO (b/298186160) replace references with the constant itself when flag is removed
*/
- private fun surfaceBehindFadeOutDurationMs(): Long {
- return if (fasterUnlockTransition()) SURFACE_BEHIND_FADE_OUT_DURATION_MS
- else LEGACY_SURFACE_BEHIND_SWIPE_FADE_DURATION_MS
- }
-
- /**
- * Temporary method for b/298186160
- * TODO (b/298186160) replace references with the constant itself when flag is removed
- */
private fun surfaceBehindFadeOutStartDelayMs(): Long {
- return if (fasterUnlockTransition()) SURFACE_BEHIND_FADE_OUT_START_DELAY_MS
+ return if (fasterUnlockTransition()) UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
else LEGACY_UNLOCK_ANIMATION_SURFACE_BEHIND_START_DELAY_MS
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 36b7ed2..73347ad 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -3143,9 +3143,13 @@
w -> w.mode == RemoteAnimationTarget.MODE_OPENING).toArray(
RemoteAnimationTarget[]::new);
+ RemoteAnimationTarget[] closingWallpapers = Arrays.stream(wallpapers).filter(
+ w -> w.mode == RemoteAnimationTarget.MODE_CLOSING).toArray(
+ RemoteAnimationTarget[]::new);
+
mKeyguardUnlockAnimationControllerLazy.get()
.notifyStartSurfaceBehindRemoteAnimation(
- openingApps, openingWallpapers, startTime,
+ openingApps, openingWallpapers, closingWallpapers, startTime,
mSurfaceBehindRemoteAnimationRequested);
} else {
mInteractionJankMonitor.begin(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
index 0863cd7..80675d3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt
@@ -28,8 +28,6 @@
const val CREATE_NOTE = "create_note"
const val DO_NOT_DISTURB = "do_not_disturb"
const val FLASHLIGHT = "flashlight"
- // TODO(b/339667383): delete or properly implement this once a product decision is made
- const val GLANCEABLE_HUB = "glanceable_hub"
const val HOME_CONTROLS = "home"
const val MUTE = "mute"
const val QR_CODE_SCANNER = "qr_code_scanner"
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
deleted file mode 100644
index 5d54126..0000000
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.keyguard.data.quickaffordance
-
-import com.android.systemui.Flags
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.shared.model.ContentDescription
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.communal.data.repository.CommunalSceneRepository
-import com.android.systemui.communal.shared.model.CommunalScenes
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
-
-/** Shortcut that opens the glanceable hub. */
-// TODO(b/339667383): delete or properly implement this once a product decision is made
-@SysUISingleton
-class GlanceableHubQuickAffordanceConfig
-@Inject
-constructor(
- private val communalRepository: CommunalSceneRepository,
-) : KeyguardQuickAffordanceConfig {
-
- override val key: String = BuiltInKeyguardQuickAffordanceKeys.GLANCEABLE_HUB
-
- override fun pickerName(): String = "Glanceable hub"
-
- override val pickerIconResourceId = R.drawable.ic_widgets
-
- override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> by lazy {
- if (Flags.glanceableHubShortcutButton()) {
- val contentDescription = ContentDescription.Loaded(pickerName())
- val icon = Icon.Resource(pickerIconResourceId, contentDescription)
- flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon))
- } else {
- flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
- }
- }
-
- override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState {
- return KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
- }
-
- override fun onTriggered(
- expandable: Expandable?
- ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
- communalRepository.changeScene(CommunalScenes.Communal, null)
- return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
index 93296f0..4556195 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -36,7 +36,6 @@
camera: CameraQuickAffordanceConfig,
doNotDisturb: DoNotDisturbQuickAffordanceConfig,
flashlight: FlashlightQuickAffordanceConfig,
- glanceableHub: GlanceableHubQuickAffordanceConfig,
home: HomeControlsKeyguardQuickAffordanceConfig,
mute: MuteQuickAffordanceConfig,
quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
@@ -47,7 +46,6 @@
camera,
doNotDisturb,
flashlight,
- glanceableHub,
home,
mute,
quickAccessWallet,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
index 0748979..796374a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLocalUserSelectionManager.kt
@@ -20,7 +20,6 @@
import android.content.Context
import android.content.IntentFilter
import android.content.SharedPreferences
-import com.android.systemui.Flags
import com.android.systemui.backup.BackupHelper
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -30,7 +29,6 @@
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
-import com.android.systemui.util.settings.SystemSettings
import javax.inject.Inject
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
@@ -52,7 +50,6 @@
@Application private val context: Context,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
- private val systemSettings: SystemSettings,
broadcastDispatcher: BroadcastDispatcher,
) : KeyguardQuickAffordanceSelectionManager {
@@ -73,22 +70,6 @@
}
private val defaults: Map<String, List<String>> by lazy {
- // Quick hack to allow testing out a lock screen shortcut to open the glanceable hub. This
- // flag will not be rolled out and is only used for local testing.
- // TODO(b/339667383): delete or properly implement this once a product decision is made
- if (Flags.glanceableHubShortcutButton()) {
- if (systemSettings.getBool("open_hub_chip_replace_home_controls", false)) {
- return@lazy mapOf(
- "bottom_start" to listOf("glanceable_hub"),
- "bottom_end" to listOf("create_note")
- )
- } else {
- return@lazy mapOf(
- "bottom_start" to listOf("home"),
- "bottom_end" to listOf("glanceable_hub")
- )
- }
- }
context.resources
.getStringArray(R.array.config_keyguardQuickAffordanceDefaults)
.associate { item ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index ab1194e..4f75e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -51,7 +51,6 @@
import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
-import com.android.systemui.util.kotlin.Utils.Companion.sampleFilter
import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
@@ -250,17 +249,20 @@
val isKeyguardGoingAway: Flow<Boolean> = repository.isKeyguardGoingAway
/** Keyguard can be clipped at the top as the shade is dragged */
- val topClippingBounds: Flow<Int?> by lazy {
- repository.topClippingBounds
- .sampleFilter(
+ val topClippingBounds: Flow<Int?> =
+ combineTransform(
keyguardTransitionInteractor
.transitionValue(scene = Scenes.Gone, stateWithoutSceneContainer = GONE)
- .onStart { emit(0f) }
- ) { goneValue ->
- goneValue != 1f
+ .map { it == 1f }
+ .onStart { emit(false) }
+ .distinctUntilChanged(),
+ repository.topClippingBounds
+ ) { isGone, topClippingBounds ->
+ if (!isGone) {
+ emit(topClippingBounds)
+ }
}
.distinctUntilChanged()
- }
/** Last point that [KeyguardRootView] view was tapped */
val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
similarity index 93%
rename from packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
index bb6215a..7a06d2f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTouchHandlingInteractor.kt
@@ -32,6 +32,7 @@
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.res.R
+import com.android.systemui.shade.PulsingGestureListener
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -51,10 +52,10 @@
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
-/** Business logic for use-cases related to the keyguard long-press feature. */
+/** Business logic for use-cases related to top-level touch handling in the lock screen. */
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
-class KeyguardLongPressInteractor
+class KeyguardTouchHandlingInteractor
@Inject
constructor(
@Application private val appContext: Context,
@@ -65,6 +66,7 @@
private val featureFlags: FeatureFlags,
broadcastDispatcher: BroadcastDispatcher,
private val accessibilityManager: AccessibilityManagerWrapper,
+ private val pulsingGestureListener: PulsingGestureListener,
) {
/** Whether the long-press handling feature should be enabled. */
val isLongPressHandlingEnabled: StateFlow<Boolean> =
@@ -166,6 +168,16 @@
_shouldOpenSettings.value = false
}
+ /** Notifies that the lockscreen has been clicked at position [x], [y]. */
+ fun onClick(x: Float, y: Float) {
+ pulsingGestureListener.onSingleTapUp(x, y)
+ }
+
+ /** Notifies that the lockscreen has been double clicked. */
+ fun onDoubleClick() {
+ pulsingGestureListener.onDoubleTapEvent()
+ }
+
private fun showSettings() {
_shouldOpenSettings.value = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index f2821a0..e063380 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -37,6 +37,10 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.clocks.AodClockBurnInModel
import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.util.ui.value
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
object KeyguardClockViewBinder {
@@ -99,15 +103,20 @@
launch {
if (!MigrateClocksToBlueprint.isEnabled) return@launch
- viewModel.isAodIconsVisible.collect {
- viewModel.currentClock.value?.let {
- if (
- viewModel.isLargeClockVisible.value && it.config.useCustomClockScene
- ) {
- blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
+ combine(
+ viewModel.hasAodIcons,
+ rootViewModel.isNotifIconContainerVisible.map { it.value }
+ ) { hasIcon, isVisible ->
+ hasIcon && isVisible
+ }
+ .distinctUntilChanged()
+ .collect { _ ->
+ viewModel.currentClock.value?.let {
+ if (it.config.useCustomClockScene) {
+ blueprintInteractor.refreshBlueprint(Type.DefaultTransition)
+ }
}
}
- }
}
launch {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
index 09fe067..057b4f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt
@@ -22,7 +22,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.app.tracing.coroutines.launch
import com.android.systemui.common.ui.view.LongPressHandlingView
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.plugins.FalsingManager
@@ -39,7 +39,7 @@
@JvmStatic
fun bind(
view: LongPressHandlingView,
- viewModel: KeyguardLongPressViewModel,
+ viewModel: KeyguardTouchHandlingViewModel,
onSingleTap: () -> Unit,
falsingManager: FalsingManager,
) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
index fa57565..4150ceb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt
@@ -26,9 +26,9 @@
import com.android.systemui.animation.ActivityTransitionAnimator
import com.android.systemui.common.ui.binder.IconViewBinder
import com.android.systemui.common.ui.binder.TextViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel
import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils
import com.android.systemui.keyguard.util.WallpaperPickerIntentUtils.LAUNCH_SOURCE_KEYGUARD
import com.android.systemui.lifecycle.repeatWhenAttached
@@ -44,7 +44,7 @@
fun bind(
view: View,
viewModel: KeyguardSettingsMenuViewModel,
- longPressViewModel: KeyguardLongPressViewModel,
+ touchHandlingViewModel: KeyguardTouchHandlingViewModel,
rootViewModel: KeyguardRootViewModel?,
vibratorHelper: VibratorHelper,
activityStarter: ActivityStarter
@@ -97,7 +97,7 @@
val hitRect = Rect()
view.getHitRect(hitRect)
if (!hitRect.contains(point.x, point.y)) {
- longPressViewModel.onTouchedOutside()
+ touchHandlingViewModel.onTouchedOutside()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 777c873..4f0ac42 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -197,8 +197,7 @@
initiallySelectedSlotId =
bundle.getString(
KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID,
- )
- ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+ ) ?: KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
shouldHighlightSelectedAffordance = shouldHighlightSelectedAffordance,
)
} else {
@@ -230,8 +229,7 @@
val previewContext =
display?.let {
ContextThemeWrapper(context.createDisplayContext(it), context.getTheme())
- }
- ?: context
+ } ?: context
val rootView = FrameLayout(previewContext)
@@ -318,8 +316,8 @@
*/
private fun setUpSmartspace(previewContext: Context, parentView: ViewGroup) {
if (
- !lockscreenSmartspaceController.isEnabled() ||
- !lockscreenSmartspaceController.isDateWeatherDecoupled()
+ !lockscreenSmartspaceController.isEnabled ||
+ !lockscreenSmartspaceController.isDateWeatherDecoupled
) {
return
}
@@ -654,6 +652,7 @@
clockController.clock = clock
}
}
+
private fun onClockChanged() {
if (MigrateClocksToBlueprint.isEnabled) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 34a1da5..0637bba 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -45,6 +45,7 @@
import com.android.systemui.plugins.clocks.ClockFaceLayout
import com.android.systemui.res.R
import com.android.systemui.shared.R as sharedR
+import com.android.systemui.util.ui.value
import dagger.Lazy
import javax.inject.Inject
@@ -70,6 +71,7 @@
private val rootViewModel: KeyguardRootViewModel,
) : KeyguardSection() {
override fun addViews(constraintLayout: ConstraintLayout) {}
+
override fun bindData(constraintLayout: ConstraintLayout) {
if (!MigrateClocksToBlueprint.isEnabled) {
return
@@ -121,35 +123,39 @@
private fun getTargetClockFace(clock: ClockController): ClockFaceLayout =
if (keyguardClockViewModel.isLargeClockVisible.value) clock.largeClock.layout
else clock.smallClock.layout
+
private fun getNonTargetClockFace(clock: ClockController): ClockFaceLayout =
if (keyguardClockViewModel.isLargeClockVisible.value) clock.smallClock.layout
else clock.largeClock.layout
fun constrainWeatherClockDateIconsBarrier(constraints: ConstraintSet) {
constraints.apply {
- if (keyguardClockViewModel.isAodIconsVisible.value) {
+ createBarrier(
+ R.id.weather_clock_bc_smartspace_bottom,
+ Barrier.BOTTOM,
+ getDimen(ENHANCED_SMARTSPACE_HEIGHT),
+ (custR.id.weather_clock_time)
+ )
+ if (
+ rootViewModel.isNotifIconContainerVisible.value.value &&
+ keyguardClockViewModel.hasAodIcons.value
+ ) {
createBarrier(
R.id.weather_clock_date_and_icons_barrier_bottom,
Barrier.BOTTOM,
0,
- *intArrayOf(sharedR.id.bc_smartspace_view, R.id.aod_notification_icon_container)
+ *intArrayOf(
+ R.id.aod_notification_icon_container,
+ R.id.weather_clock_bc_smartspace_bottom
+ )
)
} else {
- if (smartspaceViewModel.bcSmartspaceVisibility.value == VISIBLE) {
- createBarrier(
- R.id.weather_clock_date_and_icons_barrier_bottom,
- Barrier.BOTTOM,
- 0,
- (sharedR.id.bc_smartspace_view)
- )
- } else {
- createBarrier(
- R.id.weather_clock_date_and_icons_barrier_bottom,
- Barrier.BOTTOM,
- getDimen(ENHANCED_SMARTSPACE_HEIGHT),
- (R.id.lockscreen_clock_view)
- )
- }
+ createBarrier(
+ R.id.weather_clock_date_and_icons_barrier_bottom,
+ Barrier.BOTTOM,
+ 0,
+ *intArrayOf(R.id.weather_clock_bc_smartspace_bottom)
+ )
}
}
}
@@ -198,6 +204,7 @@
companion object {
private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
+
fun getDimen(context: Context, name: String): Int {
val res = context.packageManager.getResourcesForApplication(context.packageName)
val id = res.getIdentifier(name, "dimen", context.packageName)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
index 32e76d0..5cd5172 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultSettingsPopupMenuSection.kt
@@ -34,9 +34,9 @@
import com.android.systemui.keyguard.KeyguardBottomAreaRefactor
import com.android.systemui.keyguard.shared.model.KeyguardSection
import com.android.systemui.keyguard.ui.binder.KeyguardSettingsViewBinder
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
import com.android.systemui.keyguard.ui.viewmodel.KeyguardSettingsMenuViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.res.R
import com.android.systemui.statusbar.VibratorHelper
@@ -48,7 +48,7 @@
constructor(
@Main private val resources: Resources,
private val keyguardSettingsMenuViewModel: KeyguardSettingsMenuViewModel,
- private val keyguardLongPressViewModel: KeyguardLongPressViewModel,
+ private val keyguardTouchHandlingViewModel: KeyguardTouchHandlingViewModel,
private val keyguardRootViewModel: KeyguardRootViewModel,
private val vibratorHelper: VibratorHelper,
private val activityStarter: ActivityStarter,
@@ -76,7 +76,7 @@
KeyguardSettingsViewBinder.bind(
constraintLayout.requireViewById<View>(R.id.keyguard_settings_button),
keyguardSettingsMenuViewModel,
- keyguardLongPressViewModel,
+ keyguardTouchHandlingViewModel,
keyguardRootViewModel,
vibratorHelper,
activityStarter,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt
index a17c5e5..b33d552 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/KeyguardSliceViewSection.kt
@@ -35,7 +35,7 @@
) : KeyguardSection() {
override fun addViews(constraintLayout: ConstraintLayout) {
if (!MigrateClocksToBlueprint.isEnabled) return
- if (smartspaceController.isEnabled()) return
+ if (smartspaceController.isEnabled) return
constraintLayout.findViewById<View?>(R.id.keyguard_slice_view)?.let {
(it.parent as ViewGroup).removeView(it)
@@ -47,7 +47,7 @@
override fun applyConstraints(constraintSet: ConstraintSet) {
if (!MigrateClocksToBlueprint.isEnabled) return
- if (smartspaceController.isEnabled()) return
+ if (smartspaceController.isEnabled) return
constraintSet.apply {
connect(
@@ -82,7 +82,7 @@
override fun removeViews(constraintLayout: ConstraintLayout) {
if (!MigrateClocksToBlueprint.isEnabled) return
- if (smartspaceController.isEnabled()) return
+ if (smartspaceController.isEnabled) return
constraintLayout.removeView(R.id.keyguard_slice_view)
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 3e6f8e6..6fe51ae 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -44,7 +44,7 @@
private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
private val bottomAreaInteractor: KeyguardBottomAreaInteractor,
private val burnInHelperWrapper: BurnInHelperWrapper,
- private val longPressViewModel: KeyguardLongPressViewModel,
+ private val keyguardTouchHandlingViewModel: KeyguardTouchHandlingViewModel,
val settingsMenuViewModel: KeyguardSettingsMenuViewModel,
) {
data class PreviewMode(
@@ -162,7 +162,7 @@
* the lock screen settings menu item pop-up.
*/
fun onTouchedOutsideLockScreenSettingsMenu() {
- longPressViewModel.onTouchedOutside()
+ keyguardTouchHandlingViewModel.onTouchedOutside()
}
private fun button(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index f5c521a..573b75e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -31,7 +31,6 @@
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconContainerAlwaysOnDisplayViewModel
import com.android.systemui.statusbar.ui.SystemBarUtilsProxy
import javax.inject.Inject
@@ -50,7 +49,6 @@
keyguardClockInteractor: KeyguardClockInteractor,
@Application private val applicationScope: CoroutineScope,
aodNotificationIconViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
- notifsKeyguardInteractor: NotificationsKeyguardInteractor,
@get:VisibleForTesting val shadeInteractor: ShadeInteractor,
private val systemBarUtils: SystemBarUtilsProxy,
configurationInteractor: ConfigurationInteractor,
@@ -90,14 +88,13 @@
currentClock?.let { clock ->
val face = if (isLargeClock) clock.largeClock else clock.smallClock
face.config.hasCustomWeatherDataDisplay
- }
- ?: false
+ } ?: false
}
.stateIn(
scope = applicationScope,
started = SharingStarted.WhileSubscribed(),
- initialValue = currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay
- ?: false
+ initialValue =
+ currentClock.value?.largeClock?.config?.hasCustomWeatherDataDisplay ?: false
)
val clockShouldBeCentered: StateFlow<Boolean> =
@@ -109,15 +106,14 @@
// To translate elements below smartspace in weather clock to avoid overlapping between date
// element in weather clock and aod icons
- val isAodIconsVisible: StateFlow<Boolean> = combine(aodNotificationIconViewModel.icons.map {
- it.visibleIcons.isNotEmpty()
- }, notifsKeyguardInteractor.areNotificationsFullyHidden) { hasIcons, visible ->
- hasIcons && visible
- }.stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = false
- )
+ val hasAodIcons: StateFlow<Boolean> =
+ aodNotificationIconViewModel.icons
+ .map { it.visibleIcons.isNotEmpty() }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.WhileSubscribed(),
+ initialValue = false
+ )
val currentClockLayout: StateFlow<ClockLayout> =
combine(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt
index 66ceded..36a342b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSettingsMenuViewModel.kt
@@ -17,10 +17,10 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.res.R
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
-import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTouchHandlingInteractor
+import com.android.systemui.res.R
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -28,7 +28,7 @@
class KeyguardSettingsMenuViewModel
@Inject
constructor(
- private val interactor: KeyguardLongPressInteractor,
+ private val interactor: KeyguardTouchHandlingInteractor,
) {
val isVisible: Flow<Boolean> = interactor.isMenuVisible
val shouldOpenSettings: Flow<Boolean> = interactor.shouldOpenSettings
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index c0b1f95..e30ddc6 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -40,12 +40,12 @@
smartspaceInteractor: KeyguardSmartspaceInteractor,
) {
/** Whether the smartspace section is available in the build. */
- val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
+ val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled
/** Whether the weather area is available in the build. */
private val isWeatherEnabled: StateFlow<Boolean> = smartspaceInteractor.isWeatherEnabled
/** Whether the data and weather areas are decoupled in the build. */
- val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled()
+ val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled
/** Whether the date area should be visible. */
val isDateVisible: StateFlow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt
index c73931a..f1cbf25 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardTouchHandlingViewModel.kt
@@ -18,16 +18,16 @@
package com.android.systemui.keyguard.ui.viewmodel
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTouchHandlingInteractor
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
-/** Models UI state to support the lock screen long-press feature. */
+/** Models UI state to support top-level touch handling in the lock screen. */
@SysUISingleton
-class KeyguardLongPressViewModel
+class KeyguardTouchHandlingViewModel
@Inject
constructor(
- private val interactor: KeyguardLongPressInteractor,
+ private val interactor: KeyguardTouchHandlingInteractor,
) {
/** Whether the long-press handling feature should be enabled. */
@@ -45,4 +45,14 @@
fun onTouchedOutside() {
interactor.onTouchedOutside()
}
+
+ /** Notifies that the lockscreen has been clicked at position [x], [y]. */
+ fun onClick(x: Float, y: Float) {
+ interactor.onClick(x, y)
+ }
+
+ /** Notifies that the lockscreen has been double clicked. */
+ fun onDoubleClick() {
+ interactor.onDoubleClick()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index b33eaa2..1de0abe 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -44,7 +44,7 @@
clockInteractor: KeyguardClockInteractor,
private val interactor: KeyguardBlueprintInteractor,
private val authController: AuthController,
- val longPress: KeyguardLongPressViewModel,
+ val touchHandling: KeyguardTouchHandlingViewModel,
val shadeInteractor: ShadeInteractor,
@Application private val applicationScope: CoroutineScope,
unfoldTransitionInteractor: UnfoldTransitionInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 10cfd6b..630dcca 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -53,7 +53,7 @@
deviceEntryInteractor: DeviceEntryInteractor,
communalInteractor: CommunalInteractor,
shadeInteractor: ShadeInteractor,
- val longPress: KeyguardLongPressViewModel,
+ val touchHandling: KeyguardTouchHandlingViewModel,
val notifications: NotificationsPlaceholderViewModel,
) {
val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> =
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/media/controls/ui/controller/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
index 846c596..69a157f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java
@@ -585,7 +585,8 @@
/* intentSentUiThreadCallback = */ null,
buildLaunchAnimatorController(mMediaViewHolder.getPlayer()),
/* fillIntent = */ null,
- /* extraOptions = */ null);
+ /* extraOptions = */ null,
+ /* customMessage */ null);
} else {
try {
ActivityOptions opts = ActivityOptions.makeBasic();
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 2e5ff9d..b393155 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -27,11 +27,11 @@
import static com.android.systemui.accessibility.SystemActions.SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
import android.content.ContentResolver;
import android.content.Context;
@@ -74,10 +74,10 @@
import com.android.systemui.settings.DisplayTracker;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.rotation.RotationPolicyUtil;
+import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 69aa450..89dce03 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -48,8 +48,8 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
-import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_WINDOW_STATE;
import static com.android.systemui.statusbar.phone.CentralSurfaces.dumpBarTransitions;
import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay;
@@ -137,6 +137,7 @@
import com.android.systemui.shared.recents.utilities.Utilities;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.shared.rotation.RotationPolicyUtil;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.shared.system.TaskStackChangeListener;
@@ -149,7 +150,6 @@
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import com.android.systemui.statusbar.phone.AutoHideController;
-import com.android.systemui.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index a601d7f..c801662 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -19,7 +19,7 @@
import androidx.annotation.Nullable;
import com.android.internal.statusbar.RegisterStatusBarResult;
-import com.android.systemui.statusbar.phone.BarTransitions;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
/** A controller to handle navigation bars. */
public interface NavigationBarController {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt
index e73b078..06a78c5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerEmptyImpl.kt
@@ -18,7 +18,7 @@
import com.android.internal.statusbar.RegisterStatusBarResult
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.phone.BarTransitions
+import com.android.systemui.shared.statusbar.phone.BarTransitions
import javax.inject.Inject
/** A no-op version of [NavigationBarController] for variants like Arc and TV. */
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
index 1c2a087..f0207aa 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarControllerImpl.java
@@ -56,11 +56,11 @@
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.DisplayTracker;
+import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.phone.AutoHideController;
-import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.util.settings.SecureSettings;
@@ -431,6 +431,8 @@
NavigationBar navBar = mNavigationBars.get(displayId);
if (navBar != null) {
navBar.checkNavBarModes();
+ } else {
+ mTaskbarDelegate.checkNavBarModes();
}
}
@@ -439,6 +441,8 @@
NavigationBar navBar = mNavigationBars.get(displayId);
if (navBar != null) {
navBar.finishBarAnimations();
+ } else {
+ mTaskbarDelegate.finishBarAnimations();
}
}
@@ -447,6 +451,8 @@
NavigationBar navBar = mNavigationBars.get(displayId);
if (navBar != null) {
navBar.touchAutoDim();
+ } else {
+ mTaskbarDelegate.touchAutoDim();
}
}
@@ -455,6 +461,8 @@
NavigationBar navBar = mNavigationBars.get(displayId);
if (navBar != null) {
navBar.transitionTo(barMode, animate);
+ } else {
+ mTaskbarDelegate.transitionTo(barMode, animate);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
index 201e586..5442598 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarTransitions.java
@@ -28,7 +28,7 @@
import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
import com.android.systemui.res.R;
import com.android.systemui.settings.DisplayTracker;
-import com.android.systemui.statusbar.phone.BarTransitions;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
import java.io.PrintWriter;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index b360af0..d022c1c 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -24,6 +24,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.systemui.navigationbar.NavBarHelper.transitionMode;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
@@ -34,7 +35,6 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
-import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import android.app.StatusBarManager;
import android.app.StatusBarManager.WindowVisibleState;
@@ -63,13 +63,16 @@
import com.android.systemui.dump.DumpManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
@@ -117,6 +120,11 @@
boolean longPressHomeEnabled) {
updateAssistantAvailability(available, longPressHomeEnabled);
}
+
+ @Override
+ public void updateWallpaperVisibility(boolean visible, int displayId) {
+ updateWallpaperVisible(displayId, visible);
+ }
};
private int mDisabledFlags;
private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING;
@@ -150,6 +158,7 @@
private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
@Override
public void synchronizeState() {
+ checkNavBarModes();
}
@Override
@@ -165,11 +174,13 @@
private BackAnimation mBackAnimation;
- private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private final StatusBarStateController mStatusBarStateController;
@Inject
public TaskbarDelegate(Context context,
LightBarTransitionsController.Factory lightBarTransitionsControllerFactory,
- StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+ StatusBarStateController statusBarStateController) {
mLightBarTransitionsControllerFactory = lightBarTransitionsControllerFactory;
mContext = context;
@@ -179,6 +190,7 @@
};
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mStatusBarKeyguardViewManager.setTaskbarDelegate(this);
+ mStatusBarStateController = statusBarStateController;
}
public void setDependencies(CommandQueue commandQueue,
@@ -324,6 +336,68 @@
return (mSysUiState.getFlags() & View.STATUS_BAR_DISABLE_RECENT) == 0;
}
+ void onTransitionModeUpdated(int barMode, boolean checkBarModes) {
+ if (mOverviewProxyService.getProxy() == null) {
+ return;
+ }
+
+ try {
+ mOverviewProxyService.getProxy().onTransitionModeUpdated(barMode, checkBarModes);
+ } catch (RemoteException e) {
+ Log.e(TAG, "onTransitionModeUpdated() failed, barMode: " + barMode, e);
+ }
+ }
+
+ void checkNavBarModes() {
+ if (mOverviewProxyService.getProxy() == null) {
+ return;
+ }
+
+ try {
+ mOverviewProxyService.getProxy().checkNavBarModes();
+ } catch (RemoteException e) {
+ Log.e(TAG, "checkNavBarModes() failed", e);
+ }
+ }
+
+ void finishBarAnimations() {
+ if (mOverviewProxyService.getProxy() == null) {
+ return;
+ }
+
+ try {
+ mOverviewProxyService.getProxy().finishBarAnimations();
+ } catch (RemoteException e) {
+ Log.e(TAG, "finishBarAnimations() failed", e);
+ }
+ }
+
+ void touchAutoDim() {
+ if (mOverviewProxyService.getProxy() == null) {
+ return;
+ }
+
+ try {
+ int state = mStatusBarStateController.getState();
+ boolean shouldReset =
+ state != StatusBarState.KEYGUARD && state != StatusBarState.SHADE_LOCKED;
+ mOverviewProxyService.getProxy().touchAutoDim(shouldReset);
+ } catch (RemoteException e) {
+ Log.e(TAG, "touchAutoDim() failed", e);
+ }
+ }
+
+ void transitionTo(@BarTransitions.TransitionMode int barMode, boolean animate) {
+ if (mOverviewProxyService.getProxy() == null) {
+ return;
+ }
+
+ try {
+ mOverviewProxyService.getProxy().transitionTo(barMode, animate);
+ } catch (RemoteException e) {
+ Log.e(TAG, "transitionTo() failed, barMode: " + barMode, e);
+ }
+ }
private void updateAssistantAvailability(boolean assistantAvailable,
boolean longPressHomeEnabled) {
if (mOverviewProxyService.getProxy() == null) {
@@ -338,6 +412,18 @@
}
}
+ private void updateWallpaperVisible(int displayId, boolean visible) {
+ if (mOverviewProxyService.getProxy() == null) {
+ return;
+ }
+
+ try {
+ mOverviewProxyService.getProxy().updateWallpaperVisibility(displayId, visible);
+ } catch (RemoteException e) {
+ Log.e(TAG, "updateWallpaperVisibility() failed, visible: " + visible, e);
+ }
+ }
+
@Override
public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
boolean showImeSwitcher) {
@@ -465,9 +551,11 @@
private boolean updateTransitionMode(int barMode) {
if (mTransitionMode != barMode) {
mTransitionMode = barMode;
+ onTransitionModeUpdated(barMode, true);
if (mAutoHideController != null) {
mAutoHideController.touchAutoHide();
}
+
return true;
}
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
new file mode 100644
index 0000000..007ec3a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -0,0 +1,180 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package com.android.systemui.qs.panels.ui.compose
+
+import android.content.ClipData
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.draganddrop.dragAndDropSource
+import androidx.compose.foundation.draganddrop.dragAndDropTarget
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draganddrop.DragAndDropEvent
+import androidx.compose.ui.draganddrop.DragAndDropTarget
+import androidx.compose.ui.draganddrop.DragAndDropTransferData
+import androidx.compose.ui.draganddrop.mimeTypes
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+@Composable
+fun rememberDragAndDropState(listState: EditTileListState): DragAndDropState {
+ val sourceSpec: MutableState<TileSpec?> = remember { mutableStateOf(null) }
+ return remember(listState) { DragAndDropState(sourceSpec, listState) }
+}
+
+/**
+ * Holds the [TileSpec] of the tile being moved and modify the [EditTileListState] based on drag and
+ * drop events.
+ */
+class DragAndDropState(
+ val sourceSpec: MutableState<TileSpec?>,
+ private val listState: EditTileListState
+) {
+ /** Returns index of the dragged tile if it's present in the list. Returns -1 if not. */
+ fun currentPosition(): Int {
+ return sourceSpec.value?.let { listState.indexOf(it) } ?: -1
+ }
+
+ fun isMoving(tileSpec: TileSpec): Boolean {
+ return sourceSpec.value?.let { it == tileSpec } ?: false
+ }
+
+ fun onStarted(spec: TileSpec) {
+ sourceSpec.value = spec
+ }
+
+ fun onMoved(targetSpec: TileSpec) {
+ sourceSpec.value?.let { listState.move(it, targetSpec) }
+ }
+
+ fun onDrop() {
+ sourceSpec.value = null
+ }
+}
+
+/**
+ * Registers a tile as a [DragAndDropTarget] to receive drag events and update the
+ * [DragAndDropState] with the tile's position, which can be used to insert a temporary placeholder.
+ *
+ * @param dragAndDropState The [DragAndDropState] using the tiles list
+ * @param tileSpec The [TileSpec] of the tile
+ * @param acceptDrops Whether the tile should accept a drop based on a given [TileSpec]
+ * @param onDrop Action to be executed when a [TileSpec] is dropped on the tile
+ */
+@Composable
+fun Modifier.dragAndDropTile(
+ dragAndDropState: DragAndDropState,
+ tileSpec: TileSpec,
+ acceptDrops: (TileSpec) -> Boolean,
+ onDrop: (TileSpec, Int) -> Unit,
+): Modifier {
+ val target =
+ remember(dragAndDropState) {
+ object : DragAndDropTarget {
+ override fun onDrop(event: DragAndDropEvent): Boolean {
+ return dragAndDropState.sourceSpec.value?.let {
+ onDrop(it, dragAndDropState.currentPosition())
+ dragAndDropState.onDrop()
+ true
+ } ?: false
+ }
+
+ override fun onEntered(event: DragAndDropEvent) {
+ dragAndDropState.onMoved(tileSpec)
+ }
+ }
+ }
+ return dragAndDropTarget(
+ shouldStartDragAndDrop = { event ->
+ event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
+ dragAndDropState.sourceSpec.value?.let { acceptDrops(it) } ?: false
+ },
+ target = target,
+ )
+}
+
+/**
+ * Registers a tile list as a [DragAndDropTarget] to receive drop events. Use this on list
+ * containers to catch drops outside of tiles.
+ *
+ * @param dragAndDropState The [DragAndDropState] using the tiles list
+ * @param acceptDrops Whether the tile should accept a drop based on a given [TileSpec]
+ * @param onDrop Action to be executed when a [TileSpec] is dropped on the tile
+ */
+@Composable
+fun Modifier.dragAndDropTileList(
+ dragAndDropState: DragAndDropState,
+ acceptDrops: (TileSpec) -> Boolean,
+ onDrop: (TileSpec, Int) -> Unit,
+): Modifier {
+ val target =
+ remember(dragAndDropState) {
+ object : DragAndDropTarget {
+ override fun onDrop(event: DragAndDropEvent): Boolean {
+ return dragAndDropState.sourceSpec.value?.let {
+ onDrop(it, dragAndDropState.currentPosition())
+ dragAndDropState.onDrop()
+ true
+ } ?: false
+ }
+ }
+ }
+ return dragAndDropTarget(
+ target = target,
+ shouldStartDragAndDrop = { event ->
+ event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
+ dragAndDropState.sourceSpec.value?.let { acceptDrops(it) } ?: false
+ },
+ )
+}
+
+fun Modifier.dragAndDropTileSource(
+ tileSpec: TileSpec,
+ onTap: (TileSpec) -> Unit,
+ dragAndDropState: DragAndDropState
+): Modifier {
+ return dragAndDropSource {
+ detectTapGestures(
+ onTap = { onTap(tileSpec) },
+ onLongPress = {
+ dragAndDropState.onStarted(tileSpec)
+
+ // The tilespec from the ClipData transferred isn't actually needed as we're moving
+ // a tile within the same application. We're using a custom MIME type to limit the
+ // drag event to QS.
+ startTransfer(
+ DragAndDropTransferData(
+ ClipData(
+ QsDragAndDrop.CLIPDATA_LABEL,
+ arrayOf(QsDragAndDrop.TILESPEC_MIME_TYPE),
+ ClipData.Item(tileSpec.spec)
+ )
+ )
+ )
+ }
+ )
+ }
+}
+
+private object QsDragAndDrop {
+ const val CLIPDATA_LABEL = "tilespec"
+ const val TILESPEC_MIME_TYPE = "qstile/tilespec"
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
new file mode 100644
index 0000000..482c498
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.qs.panels.ui.compose
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshots.SnapshotStateList
+import androidx.compose.runtime.toMutableStateList
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+
+@Composable
+fun rememberEditListState(
+ tiles: List<EditTileViewModel>,
+): EditTileListState {
+ return remember(tiles) { EditTileListState(tiles) }
+}
+
+/** Holds the temporary state of the tile list during a drag movement where we move tiles around. */
+class EditTileListState(tiles: List<EditTileViewModel>) {
+ val tiles: SnapshotStateList<EditTileViewModel> = tiles.toMutableStateList()
+
+ fun move(tileSpec: TileSpec, target: TileSpec) {
+ val fromIndex = indexOf(tileSpec)
+ val toIndex = indexOf(target)
+
+ if (fromIndex == -1 || toIndex == -1 || fromIndex == toIndex) {
+ return
+ }
+
+ val isMovingToCurrent = tiles[toIndex].isCurrent
+ tiles.apply { add(toIndex, removeAt(fromIndex).copy(isCurrent = isMovingToCurrent)) }
+ }
+
+ fun indexOf(tileSpec: TileSpec): Int {
+ return tiles.indexOfFirst { it.tileSpec == tileSpec }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
index 7f5e474..092ad44 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
@@ -110,12 +110,15 @@
tiles: List<EditTileViewModel>,
modifier: Modifier,
onAddTile: (TileSpec, Int) -> Unit,
- onRemoveTile: (TileSpec) -> Unit
+ onRemoveTile: (TileSpec) -> Unit,
) {
val columns by viewModel.columns.collectAsStateWithLifecycle()
val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
- val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
+ val listState = rememberEditListState(tiles)
+ val dragAndDropState = rememberDragAndDropState(listState)
+
+ val (currentTiles, otherTiles) = listState.tiles.partition { it.isCurrent }
val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
}
@@ -156,20 +159,24 @@
largeTileHeight = largeTileHeight,
iconTileHeight = iconTileHeight,
tilePadding = tilePadding,
- onRemoveTile = onRemoveTile,
+ onAdd = onAddTile,
+ onRemove = onRemoveTile,
isIconOnly = viewModel::isIconTile,
columns = columns,
showLabels = showLabels,
+ dragAndDropState = dragAndDropState,
)
AvailableTiles(
- tiles = otherTiles,
+ tiles = otherTiles.filter { !dragAndDropState.isMoving(it.tileSpec) },
largeTileHeight = largeTileHeight,
iconTileHeight = iconTileHeight,
tilePadding = tilePadding,
addTileToEnd = addTileToEnd,
+ onRemove = onRemoveTile,
isIconOnly = viewModel::isIconTile,
showLabels = showLabels,
columns = columns,
+ dragAndDropState = dragAndDropState,
)
}
}
@@ -194,10 +201,12 @@
largeTileHeight: Dp,
iconTileHeight: Dp,
tilePadding: Dp,
- onRemoveTile: (TileSpec) -> Unit,
+ onAdd: (TileSpec, Int) -> Unit,
+ onRemove: (TileSpec) -> Unit,
isIconOnly: (TileSpec) -> Boolean,
showLabels: Boolean,
columns: Int,
+ dragAndDropState: DragAndDropState,
) {
val (smallTiles, largeTiles) = tiles.partition { isIconOnly(it.tileSpec) }
@@ -207,29 +216,40 @@
CurrentTilesContainer {
TileLazyGrid(
columns = GridCells.Fixed(columns),
- modifier = Modifier.height(largeGridHeight),
+ modifier =
+ Modifier.height(largeGridHeight)
+ .dragAndDropTileList(dragAndDropState, { !isIconOnly(it) }, onAdd)
) {
editTiles(
- largeTiles,
- ClickAction.REMOVE,
- onRemoveTile,
- { false },
- indicatePosition = true
+ tiles = largeTiles,
+ clickAction = ClickAction.REMOVE,
+ onClick = onRemove,
+ isIconOnly = { false },
+ dragAndDropState = dragAndDropState,
+ acceptDrops = { !isIconOnly(it) },
+ onDrop = onAdd,
+ indicatePosition = true,
)
}
}
+
CurrentTilesContainer {
TileLazyGrid(
columns = GridCells.Fixed(columns),
- modifier = Modifier.height(smallGridHeight),
+ modifier =
+ Modifier.height(smallGridHeight)
+ .dragAndDropTileList(dragAndDropState, { isIconOnly(it) }, onAdd)
) {
editTiles(
- smallTiles,
- ClickAction.REMOVE,
- onRemoveTile,
- { true },
+ tiles = smallTiles,
+ clickAction = ClickAction.REMOVE,
+ onClick = onRemove,
+ isIconOnly = { true },
showLabels = showLabels,
- indicatePosition = true
+ dragAndDropState = dragAndDropState,
+ acceptDrops = { isIconOnly(it) },
+ onDrop = onAdd,
+ indicatePosition = true,
)
}
}
@@ -242,9 +262,11 @@
iconTileHeight: Dp,
tilePadding: Dp,
addTileToEnd: (TileSpec) -> Unit,
+ onRemove: (TileSpec) -> Unit,
isIconOnly: (TileSpec) -> Boolean,
showLabels: Boolean,
columns: Int,
+ dragAndDropState: DragAndDropState,
) {
val (tilesStock, tilesCustom) = tiles.partition { it.appName == null }
val (smallTiles, largeTiles) = tilesStock.partition { isIconOnly(it.tileSpec) }
@@ -258,13 +280,27 @@
val gridHeight =
largeGridHeight + smallGridHeight + largeGridHeightCustom + (tilePadding * 2)
+ val onDrop: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, _ ->
+ onRemove(tileSpec)
+ }
+
AvailableTilesContainer {
TileLazyGrid(
columns = GridCells.Fixed(columns),
- modifier = Modifier.height(gridHeight),
+ modifier =
+ Modifier.height(gridHeight)
+ .dragAndDropTileList(dragAndDropState, { true }, onDrop)
) {
// Large tiles
- editTiles(largeTiles, ClickAction.ADD, addTileToEnd, isIconOnly)
+ editTiles(
+ largeTiles,
+ ClickAction.ADD,
+ addTileToEnd,
+ isIconOnly,
+ dragAndDropState,
+ acceptDrops = { true },
+ onDrop = onDrop,
+ )
fillUpRow(nTiles = largeTiles.size, columns = columns / 2)
// Small tiles
@@ -273,7 +309,10 @@
ClickAction.ADD,
addTileToEnd,
isIconOnly,
- showLabels = showLabels
+ showLabels = showLabels,
+ dragAndDropState = dragAndDropState,
+ acceptDrops = { true },
+ onDrop = onDrop,
)
fillUpRow(nTiles = smallTiles.size, columns = columns)
@@ -283,7 +322,10 @@
ClickAction.ADD,
addTileToEnd,
isIconOnly,
- showLabels = showLabels
+ showLabels = showLabels,
+ dragAndDropState = dragAndDropState,
+ acceptDrops = { true },
+ onDrop = onDrop,
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index bbb98d3..0bb4cfa 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -74,12 +74,12 @@
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.animation.Expandable
+import com.android.compose.modifiers.thenIf
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.common.ui.compose.load
import com.android.systemui.plugins.qs.QSTile
-import com.android.systemui.qs.panels.ui.viewmodel.AvailableEditActions
import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
import com.android.systemui.qs.panels.ui.viewmodel.TileUiState
import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
@@ -114,6 +114,7 @@
showLabels = showLabels,
label = state.label.toString(),
iconOnly = iconOnly,
+ clickEnabled = true,
onClick = tile::onClick,
onLongClick = tile::onLongClick,
modifier = modifier,
@@ -127,6 +128,7 @@
secondaryLabel = state.secondaryLabel.toString(),
icon = icon,
colors = colors,
+ clickEnabled = true,
onClick = tile::onSecondaryClick,
onLongClick = tile::onLongClick,
)
@@ -140,7 +142,7 @@
showLabels: Boolean,
label: String,
iconOnly: Boolean,
- clickEnabled: Boolean = true,
+ clickEnabled: Boolean = false,
onClick: (Expandable) -> Unit = {},
onLongClick: (Expandable) -> Unit = {},
modifier: Modifier = Modifier,
@@ -168,11 +170,12 @@
Box(
modifier =
Modifier.fillMaxSize()
- .combinedClickable(
- enabled = clickEnabled,
- onClick = { onClick(it) },
- onLongClick = { onLongClick(it) }
- )
+ .thenIf(clickEnabled) {
+ Modifier.combinedClickable(
+ onClick = { onClick(it) },
+ onLongClick = { onLongClick(it) }
+ )
+ }
.tilePadding(),
) {
content()
@@ -197,7 +200,7 @@
secondaryLabel: String?,
icon: Icon,
colors: TileColors,
- clickEnabled: Boolean = true,
+ clickEnabled: Boolean = false,
onClick: (Expandable) -> Unit = {},
onLongClick: (Expandable) -> Unit = {},
) {
@@ -212,13 +215,12 @@
) {
Box(
modifier =
- Modifier.fillMaxSize()
- .clip(TileDefaults.TileShape)
- .combinedClickable(
- enabled = clickEnabled,
+ Modifier.fillMaxSize().clip(TileDefaults.TileShape).thenIf(clickEnabled) {
+ Modifier.combinedClickable(
onClick = { onClick(it) },
onLongClick = { onLongClick(it) }
)
+ }
) {
TileIcon(
icon = icon,
@@ -269,13 +271,29 @@
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit,
) {
- val (currentTiles, otherTiles) = tiles.partition { it.isCurrent }
- val (otherTilesStock, otherTilesCustom) = otherTiles.partition { it.appName == null }
+ val currentListState = rememberEditListState(tiles)
+ val dragAndDropState = rememberDragAndDropState(currentListState)
+
+ val (currentTiles, otherTiles) = currentListState.tiles.partition { it.isCurrent }
+ val (otherTilesStock, otherTilesCustom) =
+ otherTiles
+ .filter { !dragAndDropState.isMoving(it.tileSpec) }
+ .partition { it.appName == null }
val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
}
- TileLazyGrid(modifier = modifier, columns = columns) {
+ val onDropAdd: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, position ->
+ onAddTile(tileSpec, position)
+ }
+ val onDropRemove: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, _ ->
+ onRemoveTile(tileSpec)
+ }
+
+ TileLazyGrid(
+ modifier = modifier.dragAndDropTileList(dragAndDropState, { true }, onDropAdd),
+ columns = columns
+ ) {
// These Text are just placeholders to see the different sections. Not final UI.
item(span = { GridItemSpan(maxLineSpan) }) { Text("Current tiles", color = Color.White) }
@@ -285,6 +303,9 @@
onRemoveTile,
isIconOnly,
indicatePosition = true,
+ dragAndDropState = dragAndDropState,
+ acceptDrops = { true },
+ onDrop = onDropAdd,
)
item(span = { GridItemSpan(maxLineSpan) }) { Text("Tiles to add", color = Color.White) }
@@ -294,6 +315,9 @@
ClickAction.ADD,
addTileToEnd,
isIconOnly,
+ dragAndDropState = dragAndDropState,
+ acceptDrops = { true },
+ onDrop = onDropRemove,
)
item(span = { GridItemSpan(maxLineSpan) }) {
@@ -305,6 +329,9 @@
ClickAction.ADD,
addTileToEnd,
isIconOnly,
+ dragAndDropState = dragAndDropState,
+ acceptDrops = { true },
+ onDrop = onDropRemove,
)
}
}
@@ -314,6 +341,9 @@
clickAction: ClickAction,
onClick: (TileSpec) -> Unit,
isIconOnly: (TileSpec) -> Boolean,
+ dragAndDropState: DragAndDropState,
+ acceptDrops: (TileSpec) -> Boolean,
+ onDrop: (TileSpec, Int) -> Unit,
showLabels: Boolean = false,
indicatePosition: Boolean = false,
) {
@@ -322,41 +352,44 @@
key = { tiles[it].tileSpec.spec },
span = { GridItemSpan(if (isIconOnly(tiles[it].tileSpec)) 1 else 2) },
contentType = { TileType }
- ) {
- val viewModel = tiles[it]
- val canClick =
- when (clickAction) {
- ClickAction.ADD -> AvailableEditActions.ADD in viewModel.availableEditActions
- ClickAction.REMOVE -> AvailableEditActions.REMOVE in viewModel.availableEditActions
- }
- val onClickActionName =
- when (clickAction) {
- ClickAction.ADD ->
- stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
- ClickAction.REMOVE ->
- stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
- }
- val stateDescription =
- if (indicatePosition) {
- stringResource(id = R.string.accessibility_qs_edit_position, it + 1)
- } else {
- ""
- }
-
+ ) { index ->
+ val viewModel = tiles[index]
val iconOnly = isIconOnly(viewModel.tileSpec)
val tileHeight = tileHeight(iconOnly && showLabels)
- EditTile(
- tileViewModel = viewModel,
- iconOnly = iconOnly,
- showLabels = showLabels,
- clickEnabled = canClick,
- onClick = { onClick.invoke(viewModel.tileSpec) },
- modifier =
- Modifier.height(tileHeight).animateItem().semantics {
- onClick(onClickActionName) { false }
- this.stateDescription = stateDescription
+
+ if (!dragAndDropState.isMoving(viewModel.tileSpec)) {
+ val onClickActionName =
+ when (clickAction) {
+ ClickAction.ADD ->
+ stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
+ ClickAction.REMOVE ->
+ stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
}
- )
+ val stateDescription =
+ if (indicatePosition) {
+ stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+ } else {
+ ""
+ }
+ EditTile(
+ tileViewModel = viewModel,
+ iconOnly = iconOnly,
+ showLabels = showLabels,
+ modifier =
+ Modifier.height(tileHeight)
+ .animateItem()
+ .semantics {
+ onClick(onClickActionName) { false }
+ this.stateDescription = stateDescription
+ }
+ .dragAndDropTile(dragAndDropState, viewModel.tileSpec, acceptDrops, onDrop)
+ .dragAndDropTileSource(
+ viewModel.tileSpec,
+ onClick,
+ dragAndDropState,
+ )
+ )
+ }
}
}
@@ -365,8 +398,6 @@
tileViewModel: EditTileViewModel,
iconOnly: Boolean,
showLabels: Boolean,
- clickEnabled: Boolean,
- onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val label = tileViewModel.label.load() ?: tileViewModel.tileSpec.spec
@@ -377,9 +408,6 @@
showLabels = showLabels,
label = label,
iconOnly = iconOnly,
- clickEnabled = clickEnabled,
- onClick = { onClick() },
- onLongClick = { onClick() },
modifier = modifier,
) {
if (iconOnly) {
@@ -394,9 +422,6 @@
secondaryLabel = tileViewModel.appName?.load(),
icon = tileViewModel.icon,
colors = colors,
- clickEnabled = clickEnabled,
- onClick = { onClick() },
- onLongClick = { onClick() },
)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt
index ba9a044..a4c8638 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt
@@ -26,7 +26,7 @@
* [isCurrent] indicates whether this tile is part of the current set of tiles that the user sees in
* Quick Settings.
*/
-class EditTileViewModel(
+data class EditTileViewModel(
val tileSpec: TileSpec,
val icon: Icon,
val label: Text,
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 23faf7d..4b82e0f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -62,6 +62,7 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
@@ -177,7 +178,7 @@
private boolean mBound;
private boolean mIsEnabled;
- private boolean mIsNonPrimaryUser;
+ private boolean mIsSystemOrVisibleBgUser;
private int mCurrentBoundedUserId = -1;
private boolean mInputFocusTransferStarted;
private float mInputFocusTransferStartY;
@@ -629,6 +630,7 @@
SysUiState sysUiState,
Provider<SceneInteractor> sceneInteractor,
UserTracker userTracker,
+ UserManager userManager,
WakefulnessLifecycle wakefulnessLifecycle,
UiEventLogger uiEventLogger,
DisplayTracker displayTracker,
@@ -639,10 +641,18 @@
Optional<UnfoldTransitionProgressForwarder> unfoldTransitionProgressForwarder,
BroadcastDispatcher broadcastDispatcher
) {
- // b/241601880: This component shouldn't be running for a non-primary user
- mIsNonPrimaryUser = !Process.myUserHandle().equals(UserHandle.SYSTEM);
- if (mIsNonPrimaryUser) {
- Log.wtf(TAG_OPS, "Unexpected initialization for non-primary user", new Throwable());
+ // b/241601880: This component should only be running for primary users or
+ // secondaryUsers when visibleBackgroundUsers are supported.
+ boolean isSystemUser = Process.myUserHandle().equals(UserHandle.SYSTEM);
+ boolean isVisibleBackgroundUser =
+ userManager.isVisibleBackgroundUsersSupported() && !userManager.isUserForeground();
+ if (!isSystemUser && isVisibleBackgroundUser) {
+ Log.d(TAG_OPS, "Initialization for visibleBackgroundUser");
+ }
+ mIsSystemOrVisibleBgUser = isSystemUser || isVisibleBackgroundUser;
+ if (!mIsSystemOrVisibleBgUser) {
+ Log.wtf(TAG_OPS, "Unexpected initialization for non-system foreground user",
+ new Throwable());
}
mContext = context;
@@ -833,11 +843,12 @@
}
private void internalConnectToCurrentUser(String reason) {
- if (mIsNonPrimaryUser) {
+ if (!mIsSystemOrVisibleBgUser) {
// This should not happen, but if any per-user SysUI component has a dependency on OPS,
// then this could get triggered
- Log.w(TAG_OPS, "Skipping connection to overview service due to non-primary user "
- + "caller");
+ Log.w(TAG_OPS,
+ "Skipping connection to overview service due to non-system foreground user "
+ + "caller");
return;
}
disconnectFromLauncherService(reason);
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..c4f6cd9 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsActivity.java
@@ -23,6 +23,7 @@
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.ACTION_FINISH_FROM_TRAMPOLINE;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_NAME;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CALLING_PACKAGE_TASK_ID;
+import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CLIP_DATA;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_RESULT_RECEIVER;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.PERMISSION_SELF;
@@ -46,11 +47,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 +105,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 +162,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 +251,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) {
@@ -280,11 +301,19 @@
data.putInt(Intent.EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE,
Intent.CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
data.putParcelable(EXTRA_SCREENSHOT_URI, uri);
+
+ if (mBacklinksIncludeDataCheckBox.getVisibility() == View.VISIBLE
+ && mBacklinksIncludeDataCheckBox.isChecked()
+ && mViewModel.getBacklinksLiveData().getValue() != null) {
+ ClipData backlinksData = mViewModel.getBacklinksLiveData().getValue().getClipData();
+ data.putParcelable(EXTRA_CLIP_DATA, backlinksData);
+ }
+
try {
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 +326,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/AppClipsTrampolineActivity.java b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
index 3c4469d..0161f78 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java
@@ -26,6 +26,7 @@
import android.app.Activity;
import android.content.ActivityNotFoundException;
+import android.content.ClipData;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -82,6 +83,7 @@
private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName();
static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI";
+ static final String EXTRA_CLIP_DATA = TAG + "CLIP_DATA";
static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE";
static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER";
static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME";
@@ -265,6 +267,11 @@
convertedData.setData(uri);
}
+ if (resultData.containsKey(EXTRA_CLIP_DATA)) {
+ ClipData backlinksData = resultData.getParcelable(EXTRA_CLIP_DATA, ClipData.class);
+ convertedData.setClipData(backlinksData);
+ }
+
// Broadcast no longer required, setting it to null.
mKillAppClipsBroadcastIntent = null;
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/settings/brightness/ui/binder/BrightnessMirrorInflater.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt
index 468a873..cebc59b 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/binder/BrightnessMirrorInflater.kt
@@ -21,6 +21,7 @@
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
+import androidx.core.view.setPadding
import com.android.systemui.res.R
import com.android.systemui.settings.brightness.BrightnessSliderController
@@ -33,7 +34,15 @@
val frame =
(LayoutInflater.from(context).inflate(R.layout.brightness_mirror_container, null)
as ViewGroup)
- .apply { isVisible = true }
+ .apply {
+ isVisible = true
+ // Match BrightnessMirrorController padding
+ setPadding(
+ context.resources.getDimensionPixelSize(
+ R.dimen.rounded_slider_background_padding
+ )
+ )
+ }
val sliderController = sliderControllerFactory.create(context, frame)
sliderController.init()
frame.addView(
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
index 2651a994..79e8b87 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/ui/viewModel/BrightnessMirrorViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.settings.brightness.ui.viewModel
import android.content.res.Resources
+import android.util.Log
import android.view.View
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
@@ -67,25 +68,45 @@
override fun setLocationAndSize(view: View) {
view.getLocationInWindow(tempPosition)
val padding = resources.getDimensionPixelSize(R.dimen.rounded_slider_background_padding)
- _toggleSlider?.rootView?.setPadding(padding, padding, padding, padding)
// Account for desired padding
_locationAndSize.value =
LocationAndSize(
- yOffset = tempPosition[1] - padding,
+ yOffsetFromContainer = view.findTopFromContainer() - padding,
+ yOffsetFromWindow = tempPosition[1] - padding,
width = view.measuredWidth + 2 * padding,
height = view.measuredHeight + 2 * padding,
)
}
+ private fun View.findTopFromContainer(): Int {
+ var out = 0
+ var view = this
+ while (view.id != R.id.quick_settings_container) {
+ out += view.top
+ val parent = view.parent as? View
+ if (parent == null) {
+ Log.wtf(TAG, "Couldn't find container in parents of $this")
+ break
+ }
+ view = parent
+ }
+ return out
+ }
+
// Callbacks are used for indicating reinflation when the config changes in some ways (like
// density). However, we don't need that as we recompose the view anyway
override fun addCallback(listener: MirrorController.BrightnessMirrorListener) {}
override fun removeCallback(listener: MirrorController.BrightnessMirrorListener) {}
+
+ companion object {
+ private const val TAG = "BrightnessMirrorViewModel"
+ }
}
data class LocationAndSize(
- val yOffset: Int = 0,
+ val yOffsetFromContainer: Int = 0,
+ val yOffsetFromWindow: Int = 0,
val width: Int = 0,
val height: Int = 0,
)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 8265cee..6b4e44f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -148,7 +148,7 @@
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
@@ -775,7 +775,7 @@
@Main CoroutineDispatcher mainDispatcher,
KeyguardTransitionInteractor keyguardTransitionInteractor,
DumpManager dumpManager,
- KeyguardLongPressViewModel keyguardLongPressViewModel,
+ KeyguardTouchHandlingViewModel keyguardTouchHandlingViewModel,
KeyguardInteractor keyguardInteractor,
ActivityStarter activityStarter,
SharedNotificationContainerInteractor sharedNotificationContainerInteractor,
@@ -970,7 +970,7 @@
mKeyguardClockInteractor = keyguardClockInteractor;
KeyguardLongPressViewBinder.bind(
mView.requireViewById(R.id.keyguard_long_press),
- keyguardLongPressViewModel,
+ keyguardTouchHandlingViewModel,
() -> {
onEmptySpaceClick();
return Unit.INSTANCE;
@@ -1095,7 +1095,6 @@
initBottomArea();
mWakeUpCoordinator.setStackScroller(mNotificationStackScrollLayoutController);
- mPulseExpansionHandler.setUp(mNotificationStackScrollLayoutController);
mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
@Override
public void onFullyHiddenChanged(boolean isFullyHidden) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 47fd494..1c223db 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -369,7 +369,9 @@
}
mFalsingCollector.onTouchEvent(ev);
- mPulsingWakeupGestureHandler.onTouchEvent(ev);
+ if (!SceneContainerFlag.isEnabled()) {
+ mPulsingWakeupGestureHandler.onTouchEvent(ev);
+ }
if (!SceneContainerFlag.isEnabled()
&& mGlanceableHubContainerController.onTouchEvent(ev)) {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index fe4832f0..062327d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -47,17 +47,19 @@
* display state, wake-ups are handled by [com.android.systemui.doze.DozeSensors].
*/
@SysUISingleton
-class PulsingGestureListener @Inject constructor(
- private val falsingManager: FalsingManager,
- private val dockManager: DockManager,
- private val powerInteractor: PowerInteractor,
- private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
- private val statusBarStateController: StatusBarStateController,
- private val shadeLogger: ShadeLogger,
- private val dozeInteractor: DozeInteractor,
- userTracker: UserTracker,
- tunerService: TunerService,
- dumpManager: DumpManager
+class PulsingGestureListener
+@Inject
+constructor(
+ private val falsingManager: FalsingManager,
+ private val dockManager: DockManager,
+ private val powerInteractor: PowerInteractor,
+ private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
+ private val statusBarStateController: StatusBarStateController,
+ private val shadeLogger: ShadeLogger,
+ private val dozeInteractor: DozeInteractor,
+ userTracker: UserTracker,
+ tunerService: TunerService,
+ dumpManager: DumpManager
) : GestureDetector.SimpleOnGestureListener(), Dumpable {
private var doubleTapEnabled = false
private var singleTapEnabled = false
@@ -66,21 +68,27 @@
val tunable = Tunable { key: String?, _: String? ->
when (key) {
Settings.Secure.DOZE_DOUBLE_TAP_GESTURE ->
- doubleTapEnabled = ambientDisplayConfiguration.doubleTapGestureEnabled(
- userTracker.userId)
+ doubleTapEnabled =
+ ambientDisplayConfiguration.doubleTapGestureEnabled(userTracker.userId)
Settings.Secure.DOZE_TAP_SCREEN_GESTURE ->
- singleTapEnabled = ambientDisplayConfiguration.tapGestureEnabled(
- userTracker.userId)
+ singleTapEnabled =
+ ambientDisplayConfiguration.tapGestureEnabled(userTracker.userId)
}
}
- tunerService.addTunable(tunable,
- Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
- Settings.Secure.DOZE_TAP_SCREEN_GESTURE)
+ tunerService.addTunable(
+ tunable,
+ Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
+ Settings.Secure.DOZE_TAP_SCREEN_GESTURE
+ )
dumpManager.registerDumpable(this)
}
override fun onSingleTapUp(e: MotionEvent): Boolean {
+ return onSingleTapUp(e.x, e.y)
+ }
+
+ fun onSingleTapUp(x: Float, y: Float): Boolean {
val isNotDocked = !dockManager.isDocked
shadeLogger.logSingleTapUp(statusBarStateController.isDozing, singleTapEnabled, isNotDocked)
if (statusBarStateController.isDozing && singleTapEnabled && isNotDocked) {
@@ -89,11 +97,13 @@
shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap)
if (proximityIsNotNear && isNotAFalseTap) {
shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing")
- dozeInteractor.setLastTapToWakePosition(Point(e.x.toInt(), e.y.toInt()))
+ dozeInteractor.setLastTapToWakePosition(Point(x.toInt(), y.toInt()))
powerInteractor.wakeUpIfDozing("PULSING_SINGLE_TAP", PowerManager.WAKE_REASON_TAP)
}
+
return true
}
+
shadeLogger.d("onSingleTapUp event ignored")
return false
}
@@ -103,10 +113,18 @@
* motion events for a double tap.
*/
override fun onDoubleTapEvent(e: MotionEvent): Boolean {
+ if (e.actionMasked != MotionEvent.ACTION_UP) {
+ return false
+ }
+
+ return onDoubleTapEvent()
+ }
+
+ fun onDoubleTapEvent(): Boolean {
// React to the [MotionEvent.ACTION_UP] event after double tap is detected. Falsing
// checks MUST be on the ACTION_UP event.
- if (e.actionMasked == MotionEvent.ACTION_UP &&
- statusBarStateController.isDozing &&
+ if (
+ statusBarStateController.isDozing &&
(doubleTapEnabled || singleTapEnabled) &&
!falsingManager.isProximityNear &&
!falsingManager.isFalseDoubleTap
@@ -114,6 +132,7 @@
powerInteractor.wakeUpIfDozing("PULSING_DOUBLE_TAP", PowerManager.WAKE_REASON_TAP)
return true
}
+
return false
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
index ce321dc..5065baa 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt
@@ -119,7 +119,7 @@
if (delayed) {
scope.launch {
delay(125)
- animateCollapseShadeInternal()
+ withContext(mainDispatcher) { animateCollapseShadeInternal() }
}
} else {
animateCollapseShadeInternal()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
index 3f4bcba..354d379 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/startable/ShadeStartable.kt
@@ -32,6 +32,8 @@
import com.android.systemui.shade.shared.flag.DualShade
import com.android.systemui.shade.shared.model.ShadeMode
import com.android.systemui.shade.transition.ScrimShadeTransitionController
+import com.android.systemui.statusbar.PulseExpansionHandler
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.policy.SplitShadeStateController
import javax.inject.Inject
import javax.inject.Provider
@@ -56,6 +58,8 @@
private val sceneInteractorProvider: Provider<SceneInteractor>,
private val panelExpansionInteractorProvider: Provider<PanelExpansionInteractor>,
private val shadeExpansionStateManager: ShadeExpansionStateManager,
+ private val pulseExpansionHandler: PulseExpansionHandler,
+ private val nsslc: NotificationStackScrollLayoutController,
) : CoreStartable {
override fun start() {
@@ -63,6 +67,7 @@
hydrateShadeExpansionStateManager()
logTouchesTo(touchLog)
scrimShadeTransitionController.init()
+ pulseExpansionHandler.setUp(nsslc)
}
private fun hydrateShadeExpansionStateManager() {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
index b0100b9..2b2aac64 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModel.kt
@@ -37,7 +37,6 @@
import com.android.systemui.settings.brightness.ui.viewModel.BrightnessMirrorViewModel
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shade.shared.model.ShadeMode
-import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import java.util.concurrent.atomic.AtomicBoolean
@@ -60,7 +59,6 @@
@Application private val applicationScope: CoroutineScope,
val qsSceneAdapter: QSSceneAdapter,
val shadeHeaderViewModel: ShadeHeaderViewModel,
- val notifications: NotificationsPlaceholderViewModel,
val brightnessMirrorViewModel: BrightnessMirrorViewModel,
val mediaCarouselInteractor: MediaCarouselInteractor,
shadeInteractor: ShadeInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
index 933d0ab..b467032 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/model/StatusBarMode.kt
@@ -16,13 +16,13 @@
package com.android.systemui.statusbar.data.model
-import com.android.systemui.statusbar.phone.BarTransitions
-import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
-import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
-import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE
-import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
-import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT
-import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode
+import com.android.systemui.shared.statusbar.phone.BarTransitions
+import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
+import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE
+import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
+import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT
+import com.android.systemui.shared.statusbar.phone.BarTransitions.TransitionMode
/**
* The possible status bar modes.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index af8a89d..ef4dffad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -305,26 +305,20 @@
dumpManager.registerDumpable(this)
}
- fun isEnabled(): Boolean {
- execution.assertIsMainThread()
+ val isEnabled: Boolean = plugin != null
- return plugin != null
- }
+ val isDateWeatherDecoupled: Boolean = datePlugin != null && weatherPlugin != null
- fun isDateWeatherDecoupled(): Boolean {
- execution.assertIsMainThread()
-
- return datePlugin != null && weatherPlugin != null
- }
-
- fun isWeatherEnabled(): Boolean {
- execution.assertIsMainThread()
- val showWeather = secureSettings.getIntForUser(
- LOCK_SCREEN_WEATHER_ENABLED,
- 1,
- userTracker.userId) == 1
- return showWeather
- }
+ val isWeatherEnabled: Boolean
+ get() {
+ val showWeather =
+ secureSettings.getIntForUser(
+ LOCK_SCREEN_WEATHER_ENABLED,
+ 1,
+ userTracker.userId,
+ ) == 1
+ return showWeather
+ }
private fun updateBypassEnabled() {
val bypassEnabled = bypassController.bypassEnabled
@@ -337,10 +331,10 @@
fun buildAndConnectDateView(parent: ViewGroup): View? {
execution.assertIsMainThread()
- if (!isEnabled()) {
+ if (!isEnabled) {
throw RuntimeException("Cannot build view when not enabled")
}
- if (!isDateWeatherDecoupled()) {
+ if (!isDateWeatherDecoupled) {
throw RuntimeException("Cannot build date view when not decoupled")
}
@@ -361,10 +355,10 @@
fun buildAndConnectWeatherView(parent: ViewGroup): View? {
execution.assertIsMainThread()
- if (!isEnabled()) {
+ if (!isEnabled) {
throw RuntimeException("Cannot build view when not enabled")
}
- if (!isDateWeatherDecoupled()) {
+ if (!isDateWeatherDecoupled) {
throw RuntimeException("Cannot build weather view when not decoupled")
}
@@ -385,7 +379,7 @@
fun buildAndConnectView(parent: ViewGroup): View? {
execution.assertIsMainThread()
- if (!isEnabled()) {
+ if (!isEnabled) {
throw RuntimeException("Cannot build view when not enabled")
}
@@ -577,7 +571,7 @@
}
private fun filterSmartspaceTarget(t: SmartspaceTarget): Boolean {
- if (isDateWeatherDecoupled() && t.featureType == SmartspaceTarget.FEATURE_WEATHER) {
+ if (isDateWeatherDecoupled && t.featureType == SmartspaceTarget.FEATURE_WEATHER) {
return false
}
if (!showNotifications) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index f98f77e..aff57bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -40,6 +40,7 @@
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider
import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.statusbar.notification.shared.GroupHunAnimationFix
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
@@ -726,6 +727,12 @@
*/
private fun isAttemptingToShowHun(entry: ListEntry) =
mHeadsUpManager.isHeadsUpEntry(entry.key) || isEntryBinding(entry)
+ || isHeadsUpAnimatingAway(entry)
+
+ private fun isHeadsUpAnimatingAway(entry: ListEntry): Boolean {
+ if (!GroupHunAnimationFix.isEnabled) return false
+ return entry.representativeEntry?.row?.isHeadsUpAnimatingAway ?: false
+ }
/**
* Whether the notification is already heads up/binding per [isAttemptingToShowHun] OR if it
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 367aaad..48c89f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -22,19 +22,25 @@
import android.app.Notification.CATEGORY_EVENT
import android.app.Notification.CATEGORY_REMINDER
import android.app.Notification.VISIBILITY_PRIVATE
+import android.app.NotificationManager
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.database.ContentObserver
import android.hardware.display.AmbientDisplayConfiguration
import android.os.Handler
import android.os.PowerManager
+import android.os.SystemProperties
import android.provider.Settings
import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
import android.provider.Settings.Global.HEADS_UP_OFF
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
+import com.android.internal.messages.nano.SystemMessageProto.SystemMessage
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
@@ -47,6 +53,7 @@
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.util.NotificationChannels
import com.android.systemui.util.settings.GlobalSettings
import com.android.systemui.util.settings.SystemSettings
import com.android.systemui.util.time.SystemClock
@@ -244,12 +251,22 @@
keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
}
+/**
+ * Set with:
+ * adb shell setprop persist.force_show_avalanche_edu_once 1 && adb shell stop; adb shell start
+ */
+private const val FORCE_SHOW_AVALANCHE_EDU_ONCE = "persist.force_show_avalanche_edu_once"
+
+private const val PREF_HAS_SEEN_AVALANCHE_EDU = "has_seen_avalanche_edu"
+
class AvalancheSuppressor(
private val avalancheProvider: AvalancheProvider,
private val systemClock: SystemClock,
private val systemSettings: SystemSettings,
private val packageManager: PackageManager,
private val uiEventLogger: UiEventLogger,
+ private val context: Context,
+ private val notificationManager: NotificationManager
) :
VisualInterruptionFilter(
types = setOf(PEEK, PULSE),
@@ -257,6 +274,24 @@
) {
val TAG = "AvalancheSuppressor"
+ private val prefs = context.getSharedPreferences(context.packageName, Context.MODE_PRIVATE)
+
+ // SharedPreferences are persisted across reboots
+ var hasSeenEdu: Boolean
+ get() = prefs.getBoolean(PREF_HAS_SEEN_AVALANCHE_EDU, false)
+ set(value) = prefs.edit().putBoolean(PREF_HAS_SEEN_AVALANCHE_EDU, value).apply()
+
+ // Reset on reboot.
+ // The pipeline runs these suppressors many times very fast, so we must use a separate bool
+ // to force show for debug so that phone does not get stuck sending out infinite number of
+ // education HUNs.
+ private var hasShownOnceForDebug = false
+
+ private fun shouldShowEdu() : Boolean {
+ val forceShowOnce = SystemProperties.get(FORCE_SHOW_AVALANCHE_EDU_ONCE, "").equals("1")
+ return !hasSeenEdu || (forceShowOnce && !hasShownOnceForDebug)
+ }
+
enum class State {
ALLOW_CONVERSATION_AFTER_AVALANCHE,
ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME,
@@ -309,9 +344,46 @@
if (state != State.SUPPRESS) {
return false
}
+ if (shouldShowEdu()) {
+ showEdu()
+ }
return true
}
+ /**
+ * Show avalanche education HUN from SystemUI.
+ */
+ private fun showEdu() {
+ val res = context.resources
+ val titleStr = res.getString(
+ com.android.systemui.res.R.string.adaptive_notification_edu_hun_title)
+ val textStr = res.getString(
+ com.android.systemui.res.R.string.adaptive_notification_edu_hun_text)
+ val actionStr = res.getString(
+ com.android.systemui.res.R.string.go_to_adaptive_notification_settings)
+
+ val intent = Intent(Settings.ACTION_MANAGE_ADAPTIVE_NOTIFICATIONS)
+ val pendingIntent = PendingIntent.getActivity(
+ context, 0, intent,
+ PendingIntent.FLAG_IMMUTABLE
+ )
+
+ val builder =
+ Notification.Builder(context, NotificationChannels.ALERTS)
+ .setTicker(titleStr)
+ .setContentTitle(titleStr)
+ .setContentText(textStr)
+ .setSmallIcon(com.android.systemui.res.R.drawable.ic_settings)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .setAutoCancel(true)
+ .addAction(android.R.drawable.button_onoff_indicator_off, actionStr, pendingIntent)
+ .setContentIntent(pendingIntent)
+
+ notificationManager.notify(SystemMessage.NOTE_ADAPTIVE_NOTIFICATIONS, builder.build())
+ hasSeenEdu = true
+ hasShownOnceForDebug = true;
+ }
+
private fun calculateState(entry: NotificationEntry): State {
if (
entry.ranking.isConversation &&
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 84f8662..96f94ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar.notification.interruption
+import android.app.NotificationManager
+import android.content.Context
import android.content.pm.PackageManager
import android.hardware.display.AmbientDisplayConfiguration
import android.os.Handler
@@ -68,7 +70,9 @@
private val avalancheProvider: AvalancheProvider,
private val systemSettings: SystemSettings,
private val packageManager: PackageManager,
- private val bubbles: Optional<Bubbles>
+ private val bubbles: Optional<Bubbles>,
+ private val context: Context,
+ private val notificationManager: NotificationManager
) : VisualInterruptionDecisionProvider {
init {
@@ -179,7 +183,7 @@
if (NotificationAvalancheSuppression.isEnabled) {
addFilter(
AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
- uiEventLogger)
+ uiEventLogger, context, notificationManager)
)
avalancheProvider.register()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index f73223f..4a043d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -2869,14 +2869,16 @@
}
public boolean isExpanded(boolean allowOnKeyguard) {
- // System expanded should be ignored in heads up state
final boolean isHeadsUpState = ExpandHeadsUpOnInlineReply.isEnabled()
&& canShowHeadsUp() && isHeadsUpState();
+ // System expanded should be ignored in pinned heads up state
+ final boolean isPinned = isHeadsUpState && isPinned();
// Heads Up Notification can be expanded when it is pinned.
final boolean isPinnedAndExpanded =
isHeadsUpState && isPinnedAndExpanded();
+
return (!shouldShowPublic()) && (!mOnKeyguard || allowOnKeyguard)
- && (!hasUserChangedExpansion() && !isHeadsUpState
+ && (!hasUserChangedExpansion() && !isPinned
&& (isSystemExpanded() || isSystemChildExpanded())
|| isUserExpanded() || isPinnedAndExpanded);
}
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/shared/GroupHunAnimationFix.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/GroupHunAnimationFix.kt
new file mode 100644
index 0000000..5867612
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/GroupHunAnimationFix.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.statusbar.notification.shared
+
+import com.android.systemui.Flags
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+
+/** Helper for com.android.systemui.Flags.FLAG_NOTIFICATION_GROUP_HUN_REMOVAL_ANIMATION_FIX */
+@Suppress("NOTHING_TO_INLINE")
+object GroupHunAnimationFix {
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_GROUP_HUN_REMOVAL_ANIMATION_FIX
+
+ /** A token used for dependency declaration */
+ val token: FlagToken
+ get() = FlagToken(FLAG_NAME, isEnabled)
+
+ /** Are sections sorted by time? */
+ @JvmStatic
+ inline val isEnabled
+ get() = Flags.notificationGroupHunRemovalAnimationFix()
+
+ /**
+ * Called to ensure code is only run when the flag is enabled. This protects users from the
+ * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+ * build to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun isUnexpectedlyInLegacyMode() =
+ RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+ /**
+ * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+ * the flag is enabled to ensure that the refactor author catches issues in testing.
+ */
+ @JvmStatic
+ inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
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/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index ebb0d7d..57e52b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -70,10 +70,10 @@
) { shadeExpansion, shadeMode, qsExpansion, transitionState, quickSettingsScene ->
when (transitionState) {
is ObservableTransitionState.Idle -> {
- if (transitionState.currentScene == Scenes.Lockscreen) {
- 1f
- } else {
- shadeExpansion
+ when (transitionState.currentScene) {
+ Scenes.Lockscreen,
+ Scenes.QuickSettings -> 1f
+ else -> shadeExpansion
}
}
is ObservableTransitionState.Transition -> {
@@ -162,9 +162,13 @@
stackAppearanceInteractor::setCurrentGestureOverscroll
/** Whether the notification stack is scrollable or not. */
- val isScrollable: Flow<Boolean> = sceneInteractor.currentScene.map {
- sceneInteractor.isSceneInFamily(it, SceneFamilies.NotifShade) || it == Scenes.Lockscreen
- }.dumpWhileCollecting("isScrollable")
+ val isScrollable: Flow<Boolean> =
+ sceneInteractor.currentScene
+ .map {
+ sceneInteractor.isSceneInFamily(it, SceneFamilies.NotifShade) ||
+ it == Scenes.Lockscreen
+ }
+ .dumpWhileCollecting("isScrollable")
/** Whether the notification stack is displayed in doze mode. */
val isDozing: Flow<Boolean> by lazy {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 634bd7e..08e81d5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -20,15 +20,16 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.flags.FeatureFlagsClassic
import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.shade.ui.viewmodel.ShadeSceneViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
import com.android.systemui.util.kotlin.FlowDumperImpl
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.StateFlow
/**
* ViewModel used by the Notification placeholders inside the scene container to update the
@@ -41,8 +42,8 @@
dumpManager: DumpManager,
private val interactor: NotificationStackAppearanceInteractor,
shadeInteractor: ShadeInteractor,
+ private val shadeSceneViewModel: ShadeSceneViewModel,
featureFlags: FeatureFlagsClassic,
- private val keyguardInteractor: KeyguardInteractor,
) : FlowDumperImpl(dumpManager) {
/** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */
val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES)
@@ -60,11 +61,19 @@
interactor.setConstrainedAvailableSpace(height)
}
+ /** Notifies that empty space on the notification scrim has been clicked. */
+ fun onEmptySpaceClicked() {
+ shadeSceneViewModel.onContentClicked()
+ }
+
/** Sets the content alpha for the current state of the brightness mirror */
fun setAlphaForBrightnessMirror(alpha: Float) {
interactor.setAlphaForBrightnessMirror(alpha)
}
+ /** Whether or not the notification scrim should be clickable. */
+ val isClickable: StateFlow<Boolean> = shadeSceneViewModel.isClickable
+
/** Corner rounding of the stack */
val shadeScrimRounding: Flow<ShadeScrimRounding> =
interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index fae0a46..97266c5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -126,6 +126,7 @@
animationController: ActivityTransitionAnimator.Controller?,
fillInIntent: Intent?,
extraOptions: Bundle?,
+ customMessage: String?,
) {
activityStarterInternal.startPendingIntentDismissingKeyguard(
intent = intent,
@@ -135,6 +136,7 @@
dismissShade = dismissShade,
fillInIntent = fillInIntent,
extraOptions = extraOptions,
+ customMessage = customMessage,
)
}
@@ -319,11 +321,13 @@
intent: Intent,
onlyProvisioned: Boolean,
dismissShade: Boolean,
+ customMessage: String?,
) {
activityStarterInternal.startActivityDismissingKeyguard(
intent = intent,
onlyProvisioned = onlyProvisioned,
dismissShade = dismissShade,
+ customMessage = customMessage,
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
index cff9f5e..93ce6e8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternal.kt
@@ -42,6 +42,7 @@
skipLockscreenChecks: Boolean = false,
fillInIntent: Intent? = null,
extraOptions: Bundle? = null,
+ customMessage: String? = null,
)
/** Starts an activity after dismissing keyguard. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index dbb95e6..ae98e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -42,7 +42,8 @@
showOverLockscreen: Boolean,
skipLockscreenChecks: Boolean,
fillInIntent: Intent?,
- extraOptions: Bundle?
+ extraOptions: Bundle?,
+ customMessage: String?,
) {
TODO("Not yet implemented b/308819693")
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index d75a738..0a02381 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -44,6 +44,7 @@
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.util.Compile;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 5c262f3..491db30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -181,6 +181,7 @@
import com.android.systemui.shade.ShadeSurface;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shared.recents.utilities.Utilities;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.CircleReveal;
import com.android.systemui.statusbar.CommandQueue;
@@ -1328,7 +1329,9 @@
.putExtra(Intent.EXTRA_TEXT, message.toString()),
"Share rejected touch report")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
- true /* onlyProvisioned */, true /* dismissShade */);
+ true /* onlyProvisioned */,
+ true /* dismissShade */,
+ null /* customMessage */);
});
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index c5dcb09..4ce9010 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -31,6 +31,7 @@
import com.android.internal.logging.UiEventLogger;
import com.android.internal.policy.SystemBarUtils;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
@@ -105,6 +106,8 @@
private boolean mIsExpanded;
private int mStatusBarState;
private AnimationStateHandler mAnimationStateHandler;
+
+ private Handler mBgHandler;
private int mHeadsUpInset;
// Used for determining the region for touch interaction
@@ -149,7 +152,8 @@
UiEventLogger uiEventLogger,
JavaAdapter javaAdapter,
ShadeInteractor shadeInteractor,
- AvalancheController avalancheController) {
+ AvalancheController avalancheController,
+ @Background Handler bgHandler) {
super(context, logger, handler, globalSettings, systemClock, executor,
accessibilityManagerWrapper, uiEventLogger, avalancheController);
Resources resources = mContext.getResources();
@@ -159,7 +163,7 @@
mGroupMembershipManager = groupMembershipManager;
mVisualStabilityProvider = visualStabilityProvider;
mAvalancheController = avalancheController;
-
+ mBgHandler = bgHandler;
updateResources();
configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
@Override
@@ -401,7 +405,11 @@
// Waiting HUNs in AvalancheController are still promoted to the HUN section and thus
// seen in open shade; clear them so we don't show them again when the shade closes and
// reordering is allowed again.
- mAvalancheController.logDroppedHuns(mAvalancheController.getWaitingKeys().size());
+ int waitingKeysSize = mAvalancheController.getWaitingKeys().size();
+ mBgHandler.post(() -> {
+ // Do this in the background to avoid missing frames when closing the shade
+ mAvalancheController.logDroppedHuns(waitingKeysSize);
+ });
mAvalancheController.clearNext();
// In open shade the first HUN is pinned, and visual stability logic prevents us from
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
index 8a45ec1..4aece3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
@@ -42,7 +42,7 @@
}
override fun onViewAttached() {
- if (!smartspaceRelocateToBottom() || !smartspaceController.isEnabled()) {
+ if (!smartspaceRelocateToBottom() || !smartspaceController.isEnabled) {
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index e96326a..bcb613f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -232,6 +232,7 @@
skipLockscreenChecks: Boolean,
fillInIntent: Intent?,
extraOptions: Bundle?,
+ customMessage: String?,
) {
val animationController =
if (associatedView is ExpandableNotificationRow) {
@@ -340,6 +341,7 @@
afterKeyguardGone = willLaunchResolverActivity,
dismissShade = collapse,
willAnimateOnKeyguard = animate,
+ customMessage = customMessage,
)
}
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index eec617b..a33996b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -19,8 +19,8 @@
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
import android.content.Context;
import android.graphics.Rect;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index ae3f923..6676a7f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -23,6 +23,7 @@
import android.view.View;
import com.android.systemui.res.R;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
public final class PhoneStatusBarTransitions extends BarTransitions {
private static final float ICON_ALPHA_WHEN_NOT_OPAQUE = 1;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
index 29c1372..25b8bfe0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarDemoMode.java
@@ -16,11 +16,11 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_WARNING;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSLUCENT;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_WARNING;
import static com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.OPERATOR_NAME_VIEW;
import android.annotation.NonNull;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index fad5df8..220e729 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -834,6 +834,7 @@
* @return true if the notification is sticky
*/
public boolean isSticky() {
+ if (mEntry == null) return false;
return (mEntry.isRowPinned() && mExpanded)
|| mRemoteInputActive
|| hasFullScreenIntent(mEntry);
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/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index ec9b5cf..3f1ec85 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -20,8 +20,9 @@
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
-import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DISABLE_GESTURE_SPLIT_INVOCATION;
+import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ONE_HANDED_ACTIVE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
@@ -66,6 +67,7 @@
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.sysui.ShellInterface;
@@ -249,7 +251,25 @@
pip.showPictureInPictureMenu();
}
});
+ pip.registerPipTransitionCallback(
+ new PipTransitionController.PipTransitionCallback() {
+ @Override
+ public void onPipTransitionStarted(int direction, Rect pipBounds) {
+ mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, true)
+ .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+ }
+ @Override
+ public void onPipTransitionFinished(int direction) {
+ mSysUiState.setFlag(SYSUI_STATE_DISABLE_GESTURE_PIP_ANIMATING, false)
+ .commitUpdate(mDisplayTracker.getDefaultDisplayId());
+ }
+
+ @Override
+ public void onPipTransitionCanceled(int direction) {
+ // No op.
+ }
+ }, mSysUiMainExecutor);
mSysUiState.addCallback(sysUiStateFlag -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 0ed40e9..97f5efc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics.ui.kosmos.promptViewmodel
+package com.android.systemui.biometrics.ui.viewmodel
import android.app.ActivityManager.RunningTaskInfo
import android.content.ComponentName
@@ -66,12 +66,6 @@
import com.android.systemui.biometrics.shared.model.toSensorStrength
import com.android.systemui.biometrics.shared.model.toSensorType
import com.android.systemui.biometrics.udfpsUtils
-import com.android.systemui.biometrics.ui.viewmodel.FingerprintStartMode
-import com.android.systemui.biometrics.ui.viewmodel.PromptMessage
-import com.android.systemui.biometrics.ui.viewmodel.PromptPosition
-import com.android.systemui.biometrics.ui.viewmodel.PromptSize
-import com.android.systemui.biometrics.ui.viewmodel.iconProvider
-import com.android.systemui.biometrics.ui.viewmodel.promptViewModel
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.coroutines.collectValues
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
index ddf69b5..c2173c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
@@ -132,7 +132,7 @@
public void complicationAvailability_serviceNotAvailable_noFavorites_doNotAddComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
- mDreamOverlayStateController, mControlsComponent, mMonitor, false);
+ mDreamOverlayStateController, mControlsComponent, mMonitor);
registrant.start();
setHaveFavorites(false);
@@ -145,7 +145,7 @@
public void complicationAvailability_serviceAvailable_noFavorites_doNotAddComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
- mDreamOverlayStateController, mControlsComponent, mMonitor, false);
+ mDreamOverlayStateController, mControlsComponent, mMonitor);
registrant.start();
setHaveFavorites(false);
@@ -158,7 +158,7 @@
public void complicationAvailability_serviceAvailable_noFavorites_panel_addComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
- mDreamOverlayStateController, mControlsComponent, mMonitor, false);
+ mDreamOverlayStateController, mControlsComponent, mMonitor);
registrant.start();
setHaveFavorites(false);
@@ -171,7 +171,7 @@
public void complicationAvailability_serviceNotAvailable_haveFavorites_doNotAddComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
- mDreamOverlayStateController, mControlsComponent, mMonitor, false);
+ mDreamOverlayStateController, mControlsComponent, mMonitor);
registrant.start();
setHaveFavorites(true);
@@ -184,7 +184,7 @@
public void complicationAvailability_serviceAvailable_haveFavorites_addComplication() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
- mDreamOverlayStateController, mControlsComponent, mMonitor, false);
+ mDreamOverlayStateController, mControlsComponent, mMonitor);
registrant.start();
setHaveFavorites(true);
@@ -197,7 +197,7 @@
public void complicationAvailability_checkAvailabilityWhenDreamOverlayBecomesActive() {
final DreamHomeControlsComplication.Registrant registrant =
new DreamHomeControlsComplication.Registrant(mComplication,
- mDreamOverlayStateController, mControlsComponent, mMonitor, false);
+ mDreamOverlayStateController, mControlsComponent, mMonitor);
registrant.start();
setServiceAvailable(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index e2cca38..b58eb49 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -33,13 +34,20 @@
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
+import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Color;
+import android.hardware.biometrics.BiometricManager;
+import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.Flags;
import android.media.AudioManager;
import android.os.Handler;
import android.os.UserManager;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.service.dreams.IDreamManager;
import android.testing.TestableLooper;
@@ -88,6 +96,7 @@
import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -102,6 +111,9 @@
@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class GlobalActionsDialogLiteTest extends SysuiTestCase {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
private GlobalActionsDialogLite mGlobalActionsDialogLite;
@Mock private GlobalActions.GlobalActionsManager mWindowManagerFuncs;
@@ -141,6 +153,7 @@
@Mock private DialogTransitionAnimator mDialogTransitionAnimator;
@Mock private SelectedUserInteractor mSelectedUserInteractor;
@Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
+ @Mock private BiometricManager mBiometricManager;
@Captor private ArgumentCaptor<OnBackInvokedCallback> mOnBackInvokedCallback;
private TestableLooper mTestableLooper;
@@ -157,10 +170,13 @@
when(mUserContextProvider.getUserContext()).thenReturn(mContext);
when(mResources.getConfiguration()).thenReturn(
getContext().getResources().getConfiguration());
+ when(mBiometricManager.canAuthenticate(anyInt())).thenReturn(
+ BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE);
mGlobalSettings = new FakeGlobalSettings();
mSecureSettings = new FakeSettings();
mInteractor = mKosmos.getGlobalActionsInteractor();
+ mContext.addMockSystemService(Context.BIOMETRIC_SERVICE, mBiometricManager);
mGlobalActionsDialogLite = new GlobalActionsDialogLite(mContext,
mWindowManagerFuncs,
@@ -551,6 +567,35 @@
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+ public void requestBiometricAuth_whenShutDownShortPressAndMandatoryBiometricsActive() {
+ mGlobalActionsDialogLite = spy(mGlobalActionsDialogLite);
+ ArgumentCaptor<BiometricPrompt.AuthenticationCallback>
+ authenticationCallbackArgumentCaptor = ArgumentCaptor.forClass(
+ BiometricPrompt.AuthenticationCallback.class);
+
+ when(mBiometricManager.canAuthenticate(
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS)).thenReturn(
+ BiometricManager.BIOMETRIC_SUCCESS);
+ doNothing().when(mGlobalActionsDialogLite).launchBiometricPromptForMandatoryBiometrics(
+ any());
+
+ GlobalActionsDialogLite.ShutDownAction shutDownAction =
+ mGlobalActionsDialogLite.new ShutDownAction();
+ shutDownAction.onPress();
+
+ verify(mGlobalActionsDialogLite).launchBiometricPromptForMandatoryBiometrics(
+ authenticationCallbackArgumentCaptor.capture());
+
+ BiometricPrompt.AuthenticationCallback authenticationCallback =
+ authenticationCallbackArgumentCaptor.getValue();
+ authenticationCallback.onAuthenticationSucceeded(null);
+
+ verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_SHUTDOWN_PRESS);
+ verify(mWindowManagerFuncs).shutdown();
+ }
+
+ @Test
public void testShouldLogLockdownPress() {
GlobalActionsDialogLite.LockDownAction lockDownAction =
mGlobalActionsDialogLite.new LockDownAction();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
new file mode 100644
index 0000000..6985439
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperCategoriesRepositoryTest.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.keyboard.shortcut.data.repository
+
+import android.view.KeyEvent
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shortcut.shared.model.Shortcut
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCommand
+import com.android.systemui.keyboard.shortcut.shared.model.shortcutCategory
+import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesRepository
+import com.android.systemui.keyboard.shortcut.shortcutHelperTestHelper
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShortcutHelperCategoriesRepositoryTest : SysuiTestCase() {
+ @OptIn(ExperimentalCoroutinesApi::class)
+ private val kosmos = testKosmos().also { it.testDispatcher = UnconfinedTestDispatcher() }
+ private val repo = kosmos.shortcutHelperCategoriesRepository
+ private val helper = kosmos.shortcutHelperTestHelper
+ private val testScope = kosmos.testScope
+
+ @Test
+ fun stateActive_imeShortcuts_shortcutInfoCorrectlyConverted() =
+ testScope.runTest {
+ helper.setImeShortcuts(imeShortcutsGroupWithPreviousLanguageSwitchShortcut)
+ val imeShortcutCategory by collectLastValue(repo.imeShortcutsCategory)
+
+ helper.showFromActivity()
+
+ assertThat(imeShortcutCategory)
+ .isEqualTo(expectedImeShortcutCategoryWithPreviousLanguageSwitchShortcut)
+ }
+
+ @Test
+ fun stateActive_imeShortcuts_discardUnsupportedShortcutInfoModifiers() =
+ testScope.runTest {
+ helper.setImeShortcuts(imeShortcutsGroupWithUnsupportedShortcutModifiers)
+ val imeShortcutCategory by collectLastValue(repo.imeShortcutsCategory)
+
+ helper.showFromActivity()
+
+ assertThat(imeShortcutCategory)
+ .isEqualTo(expectedImeShortcutCategoryWithDiscardedUnsupportedShortcuts)
+ }
+
+ private val switchToPreviousLanguageCommand =
+ ShortcutCommand(
+ listOf(KeyEvent.META_CTRL_ON, KeyEvent.META_SHIFT_ON, KeyEvent.KEYCODE_SPACE)
+ )
+
+ private val expectedImeShortcutCategoryWithDiscardedUnsupportedShortcuts =
+ shortcutCategory(ShortcutCategoryType.IME) { subCategory("input", emptyList()) }
+
+ private val switchToPreviousLanguageKeyboardShortcutInfo =
+ KeyboardShortcutInfo(
+ /* label = */ "switch to previous language",
+ /* keycode = */ switchToPreviousLanguageCommand.keyCodes[2],
+ /* modifiers = */ switchToPreviousLanguageCommand.keyCodes[0] or
+ switchToPreviousLanguageCommand.keyCodes[1],
+ )
+
+ private val expectedImeShortcutCategoryWithPreviousLanguageSwitchShortcut =
+ shortcutCategory(ShortcutCategoryType.IME) {
+ subCategory(
+ "input",
+ listOf(
+ Shortcut(
+ switchToPreviousLanguageKeyboardShortcutInfo.label!!.toString(),
+ listOf(switchToPreviousLanguageCommand)
+ )
+ )
+ )
+ }
+
+ private val imeShortcutsGroupWithPreviousLanguageSwitchShortcut =
+ listOf(
+ KeyboardShortcutGroup(
+ "input",
+ listOf(
+ switchToPreviousLanguageKeyboardShortcutInfo,
+ )
+ )
+ )
+
+ private val shortcutInfoWithUnsupportedModifier =
+ KeyboardShortcutInfo(
+ /* label = */ "unsupported shortcut",
+ /* keycode = */ KeyEvent.KEYCODE_SPACE,
+ /* modifiers = */ 32
+ )
+
+ private val imeShortcutsGroupWithUnsupportedShortcutModifiers =
+ listOf(
+ KeyboardShortcutGroup(
+ "input",
+ listOf(
+ shortcutInfoWithUnsupportedModifier,
+ )
+ )
+ )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
index 9c9e48e..5c7ce3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/domain/interactor/ShortcutHelperCategoriesInteractorTest.kt
@@ -16,10 +16,17 @@
package com.android.systemui.keyboard.shortcut.domain.interactor
+import android.view.KeyEvent
+import android.view.KeyboardShortcutGroup
+import android.view.KeyboardShortcutInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategory
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutCategoryType
+import com.android.systemui.keyboard.shortcut.shared.model.ShortcutSubCategory
+import com.android.systemui.keyboard.shortcut.shared.model.shortcut
import com.android.systemui.keyboard.shortcut.shortcutHelperCategoriesInteractor
import com.android.systemui.keyboard.shortcut.shortcutHelperMultiTaskingShortcutsSource
import com.android.systemui.keyboard.shortcut.shortcutHelperSystemShortcutsSource
@@ -57,6 +64,7 @@
@Test
fun categories_stateActive_emitsAllCategoriesInOrder() =
testScope.runTest {
+ helper.setImeShortcuts(imeShortcutGroups)
val categories by collectLastValue(interactor.shortcutCategories)
helper.showFromActivity()
@@ -64,7 +72,8 @@
assertThat(categories)
.containsExactly(
systemShortcutsSource.systemShortcutsCategory(),
- multitaskingShortcutsSource.multitaskingShortcutCategory()
+ multitaskingShortcutsSource.multitaskingShortcutCategory(),
+ imeShortcutCategory
)
.inOrder()
}
@@ -78,4 +87,165 @@
assertThat(categories).isEmpty()
}
+
+ fun categories_stateActive_emitsGroupedShortcuts() =
+ testScope.runTest {
+ helper.setImeShortcuts(imeShortcutsGroupsWithDuplicateLabels)
+ val categories by collectLastValue(interactor.shortcutCategories)
+
+ helper.showFromActivity()
+
+ assertThat(categories)
+ .containsExactly(
+ systemShortcutsSource.systemShortcutsCategory(),
+ multitaskingShortcutsSource.multitaskingShortcutCategory(),
+ expectedGroupedShortcutCategories
+ )
+ }
+
+ private val switchToNextLanguageShortcut =
+ shortcut(label = "switch to next language") {
+ command(KeyEvent.META_CTRL_ON, KeyEvent.KEYCODE_SPACE)
+ }
+
+ private val switchToNextLanguageKeyboardShortcutInfo =
+ KeyboardShortcutInfo(
+ /* label = */ switchToNextLanguageShortcut.label,
+ /* keycode = */ switchToNextLanguageShortcut.commands[0].keyCodes[1],
+ /* modifiers = */ switchToNextLanguageShortcut.commands[0].keyCodes[0],
+ )
+
+ private val switchToNextLanguageShortcutAlternative =
+ shortcut("switch to next language") {
+ command(KeyEvent.META_CTRL_ON, KeyEvent.KEYCODE_SPACE)
+ }
+
+ private val switchToNextLanguageKeyboardShortcutInfoAlternative =
+ KeyboardShortcutInfo(
+ /* label = */ switchToNextLanguageShortcutAlternative.label,
+ /* keycode = */ switchToNextLanguageShortcutAlternative.commands[0].keyCodes[1],
+ /* modifiers = */ switchToNextLanguageShortcutAlternative.commands[0].keyCodes[0],
+ )
+
+ private val switchToPreviousLanguageShortcut =
+ shortcut("switch to previous language") {
+ command(
+ KeyEvent.META_SHIFT_ON,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ }
+
+ private val switchToPreviousLanguageKeyboardShortcutInfo =
+ KeyboardShortcutInfo(
+ /* label = */ switchToPreviousLanguageShortcut.label,
+ /* keycode = */ switchToPreviousLanguageShortcut.commands[0].keyCodes[1],
+ /* modifiers = */ switchToPreviousLanguageShortcut.commands[0].keyCodes[0],
+ )
+
+ private val switchToPreviousLanguageShortcutAlternative =
+ shortcut("switch to previous language") {
+ command(
+ KeyEvent.META_SHIFT_ON,
+ KeyEvent.KEYCODE_SPACE,
+ )
+ }
+
+ private val switchToPreviousLanguageKeyboardShortcutInfoAlternative =
+ KeyboardShortcutInfo(
+ /* label = */ switchToPreviousLanguageShortcutAlternative.label,
+ /* keycode = */ switchToPreviousLanguageShortcutAlternative.commands[0].keyCodes[1],
+ /* modifiers = */ switchToPreviousLanguageShortcutAlternative.commands[0].keyCodes[0],
+ )
+
+ private val showOnscreenKeyboardShortcut =
+ shortcut(label = "Show on-screen keyboard") {
+ command(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_K)
+ }
+
+ private val showOnScreenKeyboardShortcutInfo =
+ KeyboardShortcutInfo(
+ /* label = */ showOnscreenKeyboardShortcut.label,
+ /* keycode = */ showOnscreenKeyboardShortcut.commands[0].keyCodes[1],
+ /* modifiers = */ showOnscreenKeyboardShortcut.commands[0].keyCodes[0],
+ )
+
+ private val accessClipboardShortcut =
+ shortcut(label = "Access clipboard") { command(KeyEvent.META_ALT_ON, KeyEvent.KEYCODE_V) }
+
+ private val accessClipboardShortcutInfo =
+ KeyboardShortcutInfo(
+ /* label = */ accessClipboardShortcut.label,
+ /* keycode = */ accessClipboardShortcut.commands[0].keyCodes[1],
+ /* modifiers = */ accessClipboardShortcut.commands[0].keyCodes[0],
+ )
+
+ private val imeShortcutGroups =
+ listOf(
+ KeyboardShortcutGroup(
+ /* label = */ "input",
+ /* shortcutInfoList = */ listOf(
+ switchToNextLanguageKeyboardShortcutInfo,
+ switchToPreviousLanguageKeyboardShortcutInfo
+ )
+ )
+ )
+
+ private val imeShortcutCategory =
+ ShortcutCategory(
+ type = ShortcutCategoryType.IME,
+ subCategories =
+ listOf(
+ ShortcutSubCategory(
+ imeShortcutGroups[0].label.toString(),
+ listOf(switchToNextLanguageShortcut, switchToPreviousLanguageShortcut)
+ )
+ )
+ )
+
+ private val imeShortcutsGroupsWithDuplicateLabels =
+ listOf(
+ KeyboardShortcutGroup(
+ "input",
+ listOf(
+ switchToNextLanguageKeyboardShortcutInfo,
+ switchToNextLanguageKeyboardShortcutInfoAlternative,
+ switchToPreviousLanguageKeyboardShortcutInfo,
+ switchToPreviousLanguageKeyboardShortcutInfoAlternative
+ )
+ ),
+ KeyboardShortcutGroup(
+ "Gboard",
+ listOf(
+ showOnScreenKeyboardShortcutInfo,
+ accessClipboardShortcutInfo,
+ )
+ )
+ )
+
+ private val expectedGroupedShortcutCategories =
+ ShortcutCategory(
+ type = ShortcutCategoryType.IME,
+ subCategories =
+ listOf(
+ ShortcutSubCategory(
+ imeShortcutsGroupsWithDuplicateLabels[0].label.toString(),
+ listOf(
+ switchToNextLanguageShortcut.copy(
+ commands =
+ switchToNextLanguageShortcut.commands +
+ switchToNextLanguageShortcutAlternative.commands
+ ),
+ switchToPreviousLanguageShortcut.copy(
+ commands =
+ switchToPreviousLanguageShortcut.commands +
+ switchToPreviousLanguageShortcutAlternative.commands
+ )
+ ),
+ ),
+ ShortcutSubCategory(
+ imeShortcutsGroupsWithDuplicateLabels[1].label.toString(),
+ listOf(showOnscreenKeyboardShortcut, accessClipboardShortcut),
+ )
+ )
+ )
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
index 90ac05f..506c5ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt
@@ -135,7 +135,6 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
- systemSettings = FakeSettings(),
broadcastDispatcher = fakeBroadcastDispatcher,
)
val remoteUserSelectionManager =
@@ -481,8 +480,7 @@
)
}
}
- }
- ?: emptyList()
+ } ?: emptyList()
}
private fun querySlots(): List<Slot> {
@@ -517,8 +515,7 @@
)
}
}
- }
- ?: emptyList()
+ } ?: emptyList()
}
private fun queryAffordances(): List<Affordance> {
@@ -558,8 +555,7 @@
)
}
}
- }
- ?: emptyList()
+ } ?: emptyList()
}
data class Slot(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 27b9863..f726aae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -7,6 +7,7 @@
import android.graphics.Rect
import android.os.PowerManager
import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
@@ -99,6 +100,13 @@
mock(ActivityManager.RunningTaskInfo::class.java), false)
private lateinit var wallpaperTargets: Array<RemoteAnimationTarget>
+ private var surfaceControlLockWp = mock(SurfaceControl::class.java)
+ private var lockWallpaperTarget = RemoteAnimationTarget(
+ 3 /* taskId */, 0, surfaceControlLockWp, false, Rect(), Rect(), 0, Point(), Rect(),
+ Rect(), mock(WindowConfiguration::class.java), false, surfaceControlLockWp,
+ Rect(), mock(ActivityManager.RunningTaskInfo::class.java), false)
+ private lateinit var lockWallpaperTargets: Array<RemoteAnimationTarget>
+
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
@@ -118,6 +126,7 @@
// appear amount setter doesn't short circuit.
remoteAnimationTargets = arrayOf(remoteTarget1)
wallpaperTargets = arrayOf(wallpaperTarget)
+ lockWallpaperTargets = arrayOf(lockWallpaperTarget)
// Set the surface applier to our mock so that we can verify the arguments passed to it.
// This applier does not have any side effects within the unlock animation controller, so
@@ -144,6 +153,7 @@
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTargets,
arrayOf(),
+ arrayOf(),
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
@@ -177,6 +187,7 @@
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTargets,
wallpaperTargets,
+ arrayOf(),
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
@@ -199,6 +210,7 @@
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTargets,
wallpaperTargets,
+ arrayOf(),
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
@@ -219,6 +231,7 @@
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTargets,
wallpaperTargets,
+ arrayOf(),
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
@@ -242,6 +255,7 @@
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTargets,
wallpaperTargets,
+ arrayOf(),
0 /* startTime */,
true /* requestedShowSurfaceBehindKeyguard */
)
@@ -265,6 +279,7 @@
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTargets,
wallpaperTargets,
+ arrayOf(),
0 /* startTime */,
true /* requestedShowSurfaceBehindKeyguard */
)
@@ -286,6 +301,7 @@
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTargets,
wallpaperTargets,
+ arrayOf(),
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
@@ -301,6 +317,7 @@
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTargets,
wallpaperTargets,
+ arrayOf(),
0 /* startTime */,
true /* requestedShowSurfaceBehindKeyguard */
)
@@ -317,6 +334,7 @@
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTargets,
wallpaperTargets,
+ arrayOf(),
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
@@ -325,6 +343,53 @@
}
/**
+ * The canned animation should launch a cross fade when there are different wallpapers on lock
+ * and home screen.
+ */
+ @Test
+ @EnableFlags(Flags.FLAG_FASTER_UNLOCK_TRANSITION)
+ fun manualUnlock_multipleWallpapers() {
+ var lastFadeInAlpha = -1f
+ var lastFadeOutAlpha = -1f
+
+ keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
+ arrayOf(remoteTarget1, remoteTarget2),
+ wallpaperTargets,
+ lockWallpaperTargets,
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
+ )
+
+ for (i in 0..10) {
+ clearInvocations(surfaceTransactionApplier)
+ val amount = i / 10f
+
+ keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(amount)
+
+ val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
+ verify(surfaceTransactionApplier, times(2)).scheduleApply(
+ captorSb.capture { sp ->
+ sp.surface == surfaceControlWp || sp.surface == surfaceControlLockWp })
+
+ val fadeInAlpha = captorSb.getLastValue { it.surface == surfaceControlWp }.alpha
+ val fadeOutAlpha = captorSb.getLastValue { it.surface == surfaceControlLockWp }.alpha
+
+ if (amount == 0f) {
+ assertTrue (fadeInAlpha == 0f)
+ assertTrue (fadeOutAlpha == 1f)
+ } else if (amount == 1f) {
+ assertTrue (fadeInAlpha == 1f)
+ assertTrue (fadeOutAlpha == 0f)
+ } else {
+ assertTrue(fadeInAlpha >= lastFadeInAlpha)
+ assertTrue(fadeOutAlpha <= lastFadeOutAlpha)
+ }
+ lastFadeInAlpha = fadeInAlpha
+ lastFadeOutAlpha = fadeOutAlpha
+ }
+ }
+
+ /**
* If we are not wake and unlocking, we expect the unlock animation to play normally.
*/
@Test
@@ -333,6 +398,7 @@
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
arrayOf(remoteTarget1, remoteTarget2),
wallpaperTargets,
+ arrayOf(),
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
@@ -378,6 +444,7 @@
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTargets,
wallpaperTargets,
+ arrayOf(),
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
@@ -387,7 +454,7 @@
clearInvocations(surfaceTransactionApplier)
keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(1f)
- keyguardUnlockAnimationController.setWallpaperAppearAmount(1f)
+ keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
verify(surfaceTransactionApplier, times(1)).scheduleApply(
@@ -414,6 +481,7 @@
keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
remoteAnimationTargets,
wallpaperTargets,
+ arrayOf(),
0 /* startTime */,
false /* requestedShowSurfaceBehindKeyguard */
)
@@ -423,7 +491,7 @@
clearInvocations(surfaceTransactionApplier)
keyguardUnlockAnimationController.setSurfaceBehindAppearAmount(1f)
- keyguardUnlockAnimationController.setWallpaperAppearAmount(1f)
+ keyguardUnlockAnimationController.setWallpaperAppearAmount(1f, wallpaperTargets)
val captorSb = ArgThatCaptor<SyncRtSurfaceTransactionApplier.SurfaceParams>()
verify(surfaceTransactionApplier, times(1)).scheduleApply(
@@ -532,8 +600,8 @@
}
}
- fun getLastValue(): T {
- return allArgs.last()
+ fun getLastValue(predicate: Predicate<T>? = null): T {
+ return if (predicate != null) allArgs.last(predicate::test) else allArgs.last()
}
fun getAllValues(): List<T> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index d2a9c58..7560a97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -63,9 +63,6 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
-import platform.test.runner.parameterized.Parameter
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -76,6 +73,9 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@FlakyTest(
@@ -281,7 +281,6 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
- systemSettings = FakeSettings(),
broadcastDispatcher = fakeBroadcastDispatcher,
)
val remoteUserSelectionManager =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index 9d06031..fd1bf54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -63,9 +63,6 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4
-import platform.test.runner.parameterized.Parameters
-import platform.test.runner.parameterized.Parameter
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
@@ -76,6 +73,9 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
@OptIn(ExperimentalCoroutinesApi::class)
@FlakyTest(
@@ -281,7 +281,6 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
- systemSettings = FakeSettings(),
broadcastDispatcher = fakeBroadcastDispatcher,
)
val remoteUserSelectionManager =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index bdc5fc3..4f4aac4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -46,8 +46,8 @@
import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
-import com.android.systemui.keyguard.domain.interactor.KeyguardLongPressInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTouchHandlingInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
@@ -58,6 +58,7 @@
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.pulsingGestureListener
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -178,7 +179,6 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
- systemSettings = FakeSettings(),
broadcastDispatcher = fakeBroadcastDispatcher,
)
val remoteUserSelectionManager =
@@ -211,8 +211,8 @@
dumpManager = mock(),
userHandle = UserHandle.SYSTEM,
)
- val keyguardLongPressInteractor =
- KeyguardLongPressInteractor(
+ val keyguardTouchHandlingInteractor =
+ KeyguardTouchHandlingInteractor(
appContext = mContext,
scope = testScope.backgroundScope,
transitionInteractor = kosmos.keyguardTransitionInteractor,
@@ -221,6 +221,7 @@
featureFlags = featureFlags,
broadcastDispatcher = broadcastDispatcher,
accessibilityManager = accessibilityManager,
+ pulsingGestureListener = kosmos.pulsingGestureListener,
)
underTest =
KeyguardBottomAreaViewModel(
@@ -246,13 +247,13 @@
),
bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
burnInHelperWrapper = burnInHelperWrapper,
- longPressViewModel =
- KeyguardLongPressViewModel(
- interactor = keyguardLongPressInteractor,
+ keyguardTouchHandlingViewModel =
+ KeyguardTouchHandlingViewModel(
+ interactor = keyguardTouchHandlingInteractor,
),
settingsMenuViewModel =
KeyguardSettingsMenuViewModel(
- interactor = keyguardLongPressInteractor,
+ interactor = keyguardTouchHandlingInteractor,
),
)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index e33d75c..9fb1aa7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -221,7 +221,6 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
- systemSettings = FakeSettings(),
broadcastDispatcher = fakeBroadcastDispatcher,
)
val remoteUserSelectionManager =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index a770722..fbfe41f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -1799,6 +1799,7 @@
any(),
eq(null),
eq(null),
+ eq(null),
)
verify(activityStarter, never()).postStartActivityDismissingKeyguard(eq(clickIntent), any())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
index fbfd35f..c7a92d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java
@@ -36,7 +36,7 @@
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.FakeDisplayTracker;
-import com.android.systemui.statusbar.phone.BarTransitions;
+import com.android.systemui.shared.statusbar.phone.BarTransitions;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
index 5ed8a11..eae6cdb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt
@@ -7,6 +7,7 @@
import com.android.systemui.dump.DumpManager
import com.android.systemui.model.SysUiState
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.recents.OverviewProxyService
import com.android.systemui.shared.system.QuickStepContract
import com.android.systemui.shared.system.TaskStackChangeListeners
@@ -70,6 +71,8 @@
lateinit var mCurrentSysUiState: NavBarHelper.CurrentSysuiState
@Mock
lateinit var mStatusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock
+ lateinit var mStatusBarStateController: StatusBarStateController
@Before
fun setup() {
@@ -80,7 +83,7 @@
`when`(mSysUiState.setFlag(anyLong(), anyBoolean())).thenReturn(mSysUiState)
mTaskStackChangeListeners = TaskStackChangeListeners.getTestInstance()
mTaskbarDelegate = TaskbarDelegate(context, mLightBarControllerFactory,
- mStatusBarKeyguardViewManager)
+ mStatusBarKeyguardViewManager, mStatusBarStateController)
mTaskbarDelegate.setDependencies(mCommandQueue, mOverviewProxyService, mNavBarHelper,
mNavigationModeController, mSysUiState, mDumpManager, mAutoHideController,
mLightBarController, mOptionalPip, mBackAnimation, mTaskStackChangeListeners)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
index 6e6e311..e1c3911 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt
@@ -23,6 +23,7 @@
import android.os.PowerManager
import android.os.Process
import android.os.UserHandle
+import android.os.UserManager
import android.testing.TestableContext
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -108,6 +109,7 @@
@Mock private lateinit var navModeController: NavigationModeController
@Mock private lateinit var statusBarWinController: NotificationShadeWindowController
@Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var userManager: UserManager
@Mock private lateinit var uiEventLogger: UiEventLogger
@Mock private lateinit var sysuiUnlockAnimationController: KeyguardUnlockAnimationController
@Mock
@@ -199,11 +201,12 @@
}
@Test
- fun connectToOverviewService_primaryUser_expectBindService() {
+ fun connectToOverviewService_primaryUserNoVisibleBgUsersSupported_expectBindService() {
val mockitoSession =
ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking()
try {
`when`(Process.myUserHandle()).thenReturn(UserHandle.SYSTEM)
+ `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false)
val spyContext = spy(context)
val ops = createOverviewProxyService(spyContext)
ops.startConnectionToCurrentUser()
@@ -214,11 +217,46 @@
}
@Test
- fun connectToOverviewService_nonPrimaryUser_expectNoBindService() {
+ fun connectToOverviewService_nonPrimaryUserNoVisibleBgUsersSupported_expectNoBindService() {
val mockitoSession =
ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking()
try {
`when`(Process.myUserHandle()).thenReturn(UserHandle.of(12345))
+ `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(false)
+ val spyContext = spy(context)
+ val ops = createOverviewProxyService(spyContext)
+ ops.startConnectionToCurrentUser()
+ verify(spyContext, times(0)).bindServiceAsUser(any(), any(), anyInt(), any())
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ @Test
+ fun connectToOverviewService_nonPrimaryBgUserVisibleBgUsersSupported_expectBindService() {
+ val mockitoSession =
+ ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking()
+ try {
+ `when`(Process.myUserHandle()).thenReturn(UserHandle.of(12345))
+ `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true)
+ `when`(userManager.isUserForeground()).thenReturn(false)
+ val spyContext = spy(context)
+ val ops = createOverviewProxyService(spyContext)
+ ops.startConnectionToCurrentUser()
+ verify(spyContext, atLeast(1)).bindServiceAsUser(any(), any(), anyInt(), any())
+ } finally {
+ mockitoSession.finishMocking()
+ }
+ }
+
+ @Test
+ fun connectToOverviewService_nonPrimaryFgUserVisibleBgUsersSupported_expectNoBindService() {
+ val mockitoSession =
+ ExtendedMockito.mockitoSession().spyStatic(Process::class.java).startMocking()
+ try {
+ `when`(Process.myUserHandle()).thenReturn(UserHandle.of(12345))
+ `when`(userManager.isVisibleBackgroundUsersSupported()).thenReturn(true)
+ `when`(userManager.isUserForeground()).thenReturn(true)
val spyContext = spy(context)
val ops = createOverviewProxyService(spyContext)
ops.startConnectionToCurrentUser()
@@ -242,6 +280,7 @@
sysUiState,
mock(),
userTracker,
+ userManager,
wakefulnessLifecycle,
uiEventLogger,
displayTracker,
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/AppClipsTrampolineActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
index 6733ead..809fb3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java
@@ -26,6 +26,7 @@
import static com.android.internal.infra.AndroidFuture.completedFuture;
import static com.android.systemui.screenshot.appclips.AppClipsEvent.SCREENSHOT_FOR_NOTE_TRIGGERED;
+import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_CLIP_DATA;
import static com.android.systemui.screenshot.appclips.AppClipsTrampolineActivity.EXTRA_SCREENSHOT_URI;
import static com.google.common.truth.Truth.assertThat;
@@ -37,6 +38,7 @@
import static org.mockito.Mockito.when;
import android.app.Activity;
+import android.content.ClipData;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -81,6 +83,7 @@
private static final String TEST_URI_STRING = "www.test-uri.com";
private static final Uri TEST_URI = Uri.parse(TEST_URI_STRING);
+ private static final ClipData TEST_CLIP_DATA = ClipData.newRawUri("Test backlinks", TEST_URI);
private static final int TEST_UID = 42;
private static final String TEST_CALLING_PACKAGE = "test-calling-package";
@@ -238,6 +241,7 @@
Bundle bundle = new Bundle();
bundle.putParcelable(EXTRA_SCREENSHOT_URI, TEST_URI);
bundle.putInt(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
+ bundle.putParcelable(EXTRA_CLIP_DATA, TEST_CLIP_DATA);
activity.getResultReceiverForTest().send(Activity.RESULT_OK, bundle);
waitForIdleSync();
@@ -245,7 +249,10 @@
assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK);
assertThat(getStatusCodeExtra(actualResult.getResultData()))
.isEqualTo(CAPTURE_CONTENT_FOR_NOTE_SUCCESS);
- assertThat(actualResult.getResultData().getData()).isEqualTo(TEST_URI);
+
+ Intent resultData = actualResult.getResultData();
+ assertThat(resultData.getData()).isEqualTo(TEST_URI);
+ assertThat(resultData.getClipData()).isEqualTo(TEST_CLIP_DATA);
assertThat(mActivityRule.getActivity().isFinishing()).isTrue();
}
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/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 15c4bfc..e7ca091 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -111,7 +111,7 @@
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingLockscreenHostedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.GoneToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.KeyguardBottomAreaViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardLongPressViewModel;
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToDreamingTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.LockscreenToOccludedTransitionViewModel;
import com.android.systemui.keyguard.ui.viewmodel.OccludedToLockscreenTransitionViewModel;
@@ -334,7 +334,7 @@
@Mock protected PrimaryBouncerToGoneTransitionViewModel
mPrimaryBouncerToGoneTransitionViewModel;
@Mock protected KeyguardTransitionInteractor mKeyguardTransitionInteractor;
- @Mock protected KeyguardLongPressViewModel mKeyuardLongPressViewModel;
+ @Mock protected KeyguardTouchHandlingViewModel mKeyuardTouchHandlingViewModel;
@Mock protected AlternateBouncerInteractor mAlternateBouncerInteractor;
@Mock protected MotionEvent mDownMotionEvent;
@Mock protected CoroutineDispatcher mMainDispatcher;
@@ -755,7 +755,7 @@
mMainDispatcher,
mKeyguardTransitionInteractor,
mDumpManager,
- mKeyuardLongPressViewModel,
+ mKeyuardTouchHandlingViewModel,
mKeyguardInteractor,
mActivityStarter,
mSharedNotificationContainerInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index e984200..a7f36c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -20,6 +20,7 @@
import android.app.Notification.CATEGORY_EVENT
import android.app.Notification.CATEGORY_REMINDER
import android.app.NotificationManager
+import android.content.pm.PackageManager.PERMISSION_DENIED
import android.content.pm.PackageManager.PERMISSION_GRANTED
import android.platform.test.annotations.EnableFlags
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -28,11 +29,16 @@
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK
import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE
-import java.util.Optional
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mockito.anyString
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
+import org.mockito.kotlin.whenever
+import java.util.Optional
@SmallTest
@RunWith(AndroidJUnit4::class)
@@ -58,7 +64,9 @@
avalancheProvider,
systemSettings,
packageManager,
- Optional.of(bubbles)
+ Optional.of(bubbles),
+ context,
+ notificationManager
)
}
@@ -87,12 +95,60 @@
// because avalanche code is based on the suppression refactor.
@Test
+ fun testAvalancheFilter_suppress_hasNotSeenEdu_showEduHun() {
+ setAllowedEmergencyPkg(false)
+ whenever(avalancheProvider.timeoutMs).thenReturn(20)
+ whenever(avalancheProvider.startTime).thenReturn(whenAgo(10))
+
+ val avalancheSuppressor = AvalancheSuppressor(
+ avalancheProvider, systemClock, systemSettings, packageManager,
+ uiEventLogger, context, notificationManager
+ )
+ avalancheSuppressor.hasSeenEdu = false
+
+ withFilter(avalancheSuppressor) {
+ ensurePeekState()
+ assertShouldNotHeadsUp(
+ buildEntry {
+ importance = NotificationManager.IMPORTANCE_HIGH
+ whenMs = whenAgo(5)
+ }
+ )
+ }
+ verify(notificationManager, times(1)).notify(anyInt(), any())
+ }
+
+ @Test
+ fun testAvalancheFilter_suppress_hasSeenEduHun_doNotShowEduHun() {
+ setAllowedEmergencyPkg(false)
+ whenever(avalancheProvider.timeoutMs).thenReturn(20)
+ whenever(avalancheProvider.startTime).thenReturn(whenAgo(10))
+
+ val avalancheSuppressor = AvalancheSuppressor(
+ avalancheProvider, systemClock, systemSettings, packageManager,
+ uiEventLogger, context, notificationManager
+ )
+ avalancheSuppressor.hasSeenEdu = true
+
+ withFilter(avalancheSuppressor) {
+ ensurePeekState()
+ assertShouldNotHeadsUp(
+ buildEntry {
+ importance = NotificationManager.IMPORTANCE_HIGH
+ whenMs = whenAgo(5)
+ }
+ )
+ }
+ verify(notificationManager, times(0)).notify(anyInt(), any())
+ }
+
+ @Test
fun testAvalancheFilter_duringAvalanche_allowConversationFromAfterEvent() {
avalancheProvider.startTime = whenAgo(10)
withFilter(
AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
- uiEventLogger)
+ uiEventLogger, context, notificationManager)
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -112,7 +168,7 @@
withFilter(
AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
- uiEventLogger)
+ uiEventLogger, context, notificationManager)
) {
ensurePeekState()
assertShouldNotHeadsUp(
@@ -132,7 +188,7 @@
withFilter(
AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
- uiEventLogger)
+ uiEventLogger, context, notificationManager)
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -150,7 +206,7 @@
withFilter(
AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
- uiEventLogger)
+ uiEventLogger, context, notificationManager)
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -168,7 +224,7 @@
withFilter(
AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
- uiEventLogger)
+ uiEventLogger, context, notificationManager)
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -186,7 +242,7 @@
withFilter(
AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
- uiEventLogger)
+ uiEventLogger, context, notificationManager)
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -204,7 +260,7 @@
withFilter(
AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
- uiEventLogger)
+ uiEventLogger, context, notificationManager)
) {
assertFsiNotSuppressed()
}
@@ -216,7 +272,7 @@
withFilter(
AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
- uiEventLogger)
+ uiEventLogger, context, notificationManager)
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -228,20 +284,24 @@
}
}
- @Test
- fun testAvalancheFilter_duringAvalanche_allowEmergency() {
- avalancheProvider.startTime = whenAgo(10)
-
+ private fun setAllowedEmergencyPkg(allow: Boolean) {
`when`(
packageManager.checkPermission(
org.mockito.Mockito.eq(permission.RECEIVE_EMERGENCY_BROADCAST),
anyString()
)
- ).thenReturn(PERMISSION_GRANTED)
+ ).thenReturn(if (allow) PERMISSION_GRANTED else PERMISSION_DENIED)
+ }
+
+ @Test
+ fun testAvalancheFilter_duringAvalanche_allowEmergency() {
+ avalancheProvider.startTime = whenAgo(10)
+
+ setAllowedEmergencyPkg(true)
withFilter(
AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
- uiEventLogger)
+ uiEventLogger, context, notificationManager)
) {
ensurePeekState()
assertShouldHeadsUp(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
index a457405..378705a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestBase.kt
@@ -31,6 +31,7 @@
import android.app.Notification.GROUP_ALERT_SUMMARY
import android.app.Notification.VISIBILITY_PRIVATE
import android.app.NotificationChannel
+import android.app.NotificationManager
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.IMPORTANCE_LOW
@@ -133,7 +134,7 @@
protected val bubbles: Bubbles = mock()
lateinit var systemSettings: SystemSettings
protected val packageManager: PackageManager = mock()
-
+ protected val notificationManager: NotificationManager = mock()
protected abstract val provider: VisualInterruptionDecisionProvider
private val neverSuppresses = object : NotificationInterruptSuppressor {}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
index 01e638b..f4cebd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderTestUtil.kt
@@ -15,6 +15,8 @@
*/
package com.android.systemui.statusbar.notification.interruption
+import android.app.NotificationManager
+import android.content.Context
import android.content.pm.PackageManager
import android.hardware.display.AmbientDisplayConfiguration
import android.os.Handler
@@ -58,6 +60,8 @@
systemSettings: SystemSettings,
packageManager: PackageManager,
bubbles: Optional<Bubbles>,
+ context: Context,
+ notificationManager: NotificationManager
): VisualInterruptionDecisionProvider {
return if (VisualInterruptionRefactor.isEnabled) {
VisualInterruptionDecisionProviderImpl(
@@ -79,7 +83,9 @@
avalancheProvider,
systemSettings,
packageManager,
- bubbles
+ bubbles,
+ context,
+ notificationManager
)
} else {
NotificationInterruptStateProviderWrapper(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index 164a06e..e8349b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -862,11 +862,12 @@
@Test
@EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
- public void isExpanded_systemExpandedTrueForHeadsUp_notExpanded() throws Exception {
+ public void isExpanded_HUNsystemExpandedTrueForPinned_notExpanded() throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
row.setOnKeyguard(false);
row.setSystemExpanded(true);
+ row.setPinned(true);
row.setHeadsUp(true);
// THEN
@@ -875,12 +876,27 @@
@Test
@EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
- public void isExpanded_systemExpandedTrueForHeadsUpDisappearRunning_notExpanded()
+ public void isExpanded_HUNsystemExpandedTrueForNotPinned_expanded() throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setOnKeyguard(false);
+ row.setSystemExpanded(true);
+ row.setPinned(false);
+ row.setHeadsUp(true);
+
+ // THEN
+ assertThat(row.isExpanded()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
+ public void isExpanded_HUNDisappearingsystemExpandedTrueForPinned_notExpanded()
throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
row.setOnKeyguard(false);
row.setSystemExpanded(true);
+ row.setPinned(true);
row.setHeadsUpAnimatingAway(true);
// THEN
@@ -889,6 +905,21 @@
@Test
@EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
+ public void isExpanded_HUNDisappearingsystemExpandedTrueForNotPinned_expanded()
+ throws Exception {
+ // GIVEN
+ final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
+ row.setOnKeyguard(false);
+ row.setSystemExpanded(true);
+ row.setPinned(false);
+ row.setHeadsUpAnimatingAway(true);
+
+ // THEN
+ assertThat(row.isExpanded()).isTrue();
+ }
+
+ @Test
+ @EnableFlags(ExpandHeadsUpOnInlineReply.FLAG_NAME)
public void isExpanded_userExpandedTrueForHeadsUp_expanded() throws Exception {
// GIVEN
final ExpandableNotificationRow row = mNotificationTestHelper.createRow();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 1eb33ce..d2540a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -53,6 +53,7 @@
import android.app.ActivityManager;
import android.app.IWallpaperManager;
+import android.app.NotificationManager;
import android.app.WallpaperManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
@@ -339,6 +340,7 @@
@Mock private KeyboardShortcuts mKeyboardShortcuts;
@Mock private KeyboardShortcutListSearch mKeyboardShortcutListSearch;
@Mock private PackageManager mPackageManager;
+ @Mock private NotificationManager mNotificationManager;
@Mock private GlanceableHubContainerController mGlanceableHubContainerController;
@Mock private EmergencyGestureIntentFactory mEmergencyGestureIntentFactory;
@@ -399,7 +401,9 @@
mAvalancheProvider,
mSystemSettings,
mPackageManager,
- Optional.of(mBubbles));
+ Optional.of(mBubbles),
+ mContext,
+ mNotificationManager);
mVisualInterruptionDecisionProvider.start();
mContext.addMockSystemService(TrustManager.class, mock(TrustManager.class));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index a27073c..88ec18d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -18,7 +18,7 @@
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import static com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
import static junit.framework.Assert.assertTrue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
index c4568a9..318656b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitionsTest.kt
@@ -21,12 +21,12 @@
import androidx.test.filters.SmallTest
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
-import com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
-import com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE
-import com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
-import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSLUCENT
-import com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT
+import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT
+import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT
+import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_OPAQUE
+import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT
+import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSLUCENT
+import com.android.systemui.shared.statusbar.phone.BarTransitions.MODE_TRANSPARENT
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
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/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index c5fbc39..dc7a2c3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -59,6 +59,7 @@
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
+import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -473,7 +474,9 @@
mock(AvalancheProvider.class),
mock(SystemSettings.class),
mock(PackageManager.class),
- Optional.of(mock(Bubbles.class))
+ Optional.of(mock(Bubbles.class)),
+ mContext,
+ mock(NotificationManager.class)
);
interruptionDecisionProvider.start();
diff --git a/packages/SystemUI/tests/utils/src/android/hardware/display/AmbientDisplayConfigurationKosmos.kt b/packages/SystemUI/tests/utils/src/android/hardware/display/AmbientDisplayConfigurationKosmos.kt
new file mode 100644
index 0000000..3f3c30f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/hardware/display/AmbientDisplayConfigurationKosmos.kt
@@ -0,0 +1,25 @@
+/*
+ * 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 android.hardware.display
+
+import android.content.applicationContext
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+
+val Kosmos.ambientDisplayConfiguration by Fixture {
+ FakeAmbientDisplayConfiguration(applicationContext)
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
index f51036f..e00f980 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/KeyboardShortcutHelperKosmos.kt
@@ -19,6 +19,7 @@
import android.content.applicationContext
import android.content.res.mainResources
import android.hardware.input.fakeInputManager
+import android.view.windowManager
import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperCategoriesRepository
import com.android.systemui.keyboard.shortcut.data.repository.ShortcutHelperStateRepository
@@ -59,6 +60,8 @@
ShortcutHelperCategoriesRepository(
shortcutHelperSystemShortcutsSource,
shortcutHelperMultiTaskingShortcutsSource,
+ windowManager,
+ shortcutHelperStateRepository
)
}
@@ -68,7 +71,8 @@
shortcutHelperStateRepository,
applicationContext,
broadcastDispatcher,
- fakeCommandQueue
+ fakeCommandQueue,
+ windowManager
)
}
@@ -83,12 +87,7 @@
}
val Kosmos.shortcutHelperCategoriesInteractor by
- Kosmos.Fixture {
- ShortcutHelperCategoriesInteractor(
- shortcutHelperStateRepository,
- shortcutHelperCategoriesRepository
- )
- }
+ Kosmos.Fixture { ShortcutHelperCategoriesInteractor(shortcutHelperCategoriesRepository) }
val Kosmos.shortcutHelperViewModel by
Kosmos.Fixture { ShortcutHelperViewModel(testDispatcher, shortcutHelperStateInteractor) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
index 36608ff..40510db 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyboard/shortcut/data/repository/ShortcutHelperTestHelper.kt
@@ -18,20 +18,45 @@
import android.content.Context
import android.content.Intent
+import android.view.KeyboardShortcutGroup
+import android.view.WindowManager
+import android.view.WindowManager.KeyboardShortcutsReceiver
import com.android.systemui.broadcast.FakeBroadcastDispatcher
import com.android.systemui.keyguard.data.repository.FakeCommandQueue
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
class ShortcutHelperTestHelper(
repo: ShortcutHelperStateRepository,
private val context: Context,
private val fakeBroadcastDispatcher: FakeBroadcastDispatcher,
private val fakeCommandQueue: FakeCommandQueue,
+ windowManager: WindowManager
) {
+ companion object {
+ const val DEFAULT_DEVICE_ID = 123
+ }
+
+ private var imeShortcuts: List<KeyboardShortcutGroup> = emptyList()
+
init {
+ whenever(windowManager.requestImeKeyboardShortcuts(any(), any())).thenAnswer {
+ val keyboardShortcutReceiver = it.getArgument<KeyboardShortcutsReceiver>(0)
+ keyboardShortcutReceiver.onKeyboardShortcutsReceived(imeShortcuts)
+ return@thenAnswer Unit
+ }
repo.start()
}
+ /**
+ * Use this method to set what ime shortcuts should be returned from windowManager in tests. By
+ * default windowManager.requestImeKeyboardShortcuts will return emptyList. See init block.
+ */
+ fun setImeShortcuts(imeShortcuts: List<KeyboardShortcutGroup>) {
+ this.imeShortcuts = imeShortcuts
+ }
+
fun hideThroughCloseSystemDialogs() {
fakeBroadcastDispatcher.sendIntentToMatchingReceiversOnly(
context,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
index c06f833..73799b6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardLongPressInteractorKosmos.kt
@@ -24,10 +24,11 @@
import com.android.systemui.keyguard.data.repository.keyguardRepository
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.shade.pulsingGestureListener
-val Kosmos.keyguardLongPressInteractor by
+val Kosmos.keyguardTouchHandlingInteractor by
Kosmos.Fixture {
- KeyguardLongPressInteractor(
+ KeyguardTouchHandlingInteractor(
appContext = applicationContext,
scope = applicationCoroutineScope,
transitionInteractor = keyguardTransitionInteractor,
@@ -36,5 +37,6 @@
featureFlags = featureFlagsClassic,
broadcastDispatcher = broadcastDispatcher,
accessibilityManager = accessibilityManagerWrapper,
+ pulsingGestureListener = pulsingGestureListener,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
index 3c62b44..b5e6f75 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
@@ -23,7 +23,6 @@
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.shade.domain.interactor.shadeInteractor
import com.android.systemui.statusbar.notification.icon.ui.viewmodel.notificationIconContainerAlwaysOnDisplayViewModel
-import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
import com.android.systemui.statusbar.ui.systemBarUtilsProxy
val Kosmos.keyguardClockViewModel by
@@ -32,7 +31,6 @@
keyguardClockInteractor = keyguardClockInteractor,
applicationScope = applicationCoroutineScope,
aodNotificationIconViewModel = notificationIconContainerAlwaysOnDisplayViewModel,
- notifsKeyguardInteractor = notificationsKeyguardInteractor,
shadeInteractor = shadeInteractor,
systemBarUtils = systemBarUtilsProxy,
configurationInteractor = configurationInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt
index 3c9846a..281d7b0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardLongPressViewModelKosmos.kt
@@ -16,12 +16,12 @@
package com.android.systemui.keyguard.ui.viewmodel
-import com.android.systemui.keyguard.domain.interactor.keyguardLongPressInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTouchHandlingInteractor
import com.android.systemui.kosmos.Kosmos
-val Kosmos.keyguardLongPressViewModel by
+val Kosmos.keyguardTouchHandlingViewModel by
Kosmos.Fixture {
- KeyguardLongPressViewModel(
- interactor = keyguardLongPressInteractor,
+ KeyguardTouchHandlingViewModel(
+ interactor = keyguardTouchHandlingInteractor,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
index 30a4f21..24e47b0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModelKosmos.kt
@@ -30,7 +30,7 @@
clockInteractor = keyguardClockInteractor,
interactor = keyguardBlueprintInteractor,
authController = authController,
- longPress = keyguardLongPressViewModel,
+ touchHandling = keyguardTouchHandlingViewModel,
shadeInteractor = shadeInteractor,
applicationScope = applicationCoroutineScope,
unfoldTransitionInteractor = unfoldTransitionInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/PulsingGestureListenerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/PulsingGestureListenerKosmos.kt
new file mode 100644
index 0000000..4fc2228
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/PulsingGestureListenerKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * 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.shade
+
+import android.hardware.display.ambientDisplayConfiguration
+import com.android.systemui.classifier.falsingManager
+import com.android.systemui.dock.dockManager
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.keyguard.domain.interactor.dozeInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import com.android.systemui.plugins.statusbar.statusBarStateController
+import com.android.systemui.power.domain.interactor.powerInteractor
+import com.android.systemui.settings.userTracker
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.pulsingGestureListener by Fixture {
+ PulsingGestureListener(
+ falsingManager = falsingManager,
+ dockManager = dockManager,
+ powerInteractor = powerInteractor,
+ ambientDisplayConfiguration = ambientDisplayConfiguration,
+ statusBarStateController = statusBarStateController,
+ shadeLogger = mock(),
+ dozeInteractor = dozeInteractor,
+ userTracker = userTracker,
+ tunerService = mock(),
+ dumpManager = dumpManager,
+ )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
index b85858d..79b80bc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/startable/ShadeStartableKosmos.kt
@@ -27,7 +27,9 @@
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.shade.domain.interactor.panelExpansionInteractor
import com.android.systemui.shade.transition.ScrimShadeTransitionController
+import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
import com.android.systemui.statusbar.policy.splitShadeStateController
+import com.android.systemui.statusbar.pulseExpansionHandler
import com.android.systemui.util.mockito.mock
@Deprecated("ShadeExpansionStateManager is deprecated. Remove your dependency on it instead.")
@@ -45,5 +47,7 @@
sceneInteractorProvider = { sceneInteractor },
panelExpansionInteractorProvider = { panelExpansionInteractor },
shadeExpansionStateManager = shadeExpansionStateManager,
+ pulseExpansionHandler = pulseExpansionHandler,
+ nsslc = notificationStackScrollLayoutController,
)
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
new file mode 100644
index 0000000..989c3a5
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelKosmos.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade.ui.viewmodel
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.media.controls.domain.pipeline.interactor.mediaCarouselInteractor
+import com.android.systemui.qs.footerActionsController
+import com.android.systemui.qs.footerActionsViewModelFactory
+import com.android.systemui.qs.ui.adapter.qsSceneAdapter
+import com.android.systemui.scene.domain.interactor.sceneInteractor
+import com.android.systemui.settings.brightness.ui.viewmodel.brightnessMirrorViewModel
+import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.unfold.domain.interactor.unfoldTransitionInteractor
+
+val Kosmos.shadeSceneViewModel: ShadeSceneViewModel by
+ Kosmos.Fixture {
+ ShadeSceneViewModel(
+ applicationScope = applicationCoroutineScope,
+ shadeHeaderViewModel = shadeHeaderViewModel,
+ qsSceneAdapter = qsSceneAdapter,
+ brightnessMirrorViewModel = brightnessMirrorViewModel,
+ mediaCarouselInteractor = mediaCarouselInteractor,
+ shadeInteractor = shadeInteractor,
+ footerActionsViewModelFactory = footerActionsViewModelFactory,
+ footerActionsController = footerActionsController,
+ sceneInteractor = sceneInteractor,
+ unfoldTransitionInteractor = unfoldTransitionInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index f0eea38..a0e9303 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -18,10 +18,10 @@
import com.android.systemui.dump.dumpManager
import com.android.systemui.flags.featureFlagsClassic
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.shade.ui.viewmodel.shadeSceneViewModel
import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationStackAppearanceInteractor
val Kosmos.notificationsPlaceholderViewModel by Fixture {
@@ -29,7 +29,7 @@
dumpManager = dumpManager,
interactor = notificationStackAppearanceInteractor,
shadeInteractor = shadeInteractor,
+ shadeSceneViewModel = shadeSceneViewModel,
featureFlags = featureFlagsClassic,
- keyguardInteractor = keyguardInteractor,
)
}
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/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index f3172ae..bdc3577 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -274,7 +274,9 @@
android.telephony.ModemActivityInfo
android.telephony.ServiceState
+android.os.connectivity.CellularBatteryStats
android.os.connectivity.WifiActivityEnergyInfo
+android.os.connectivity.WifiBatteryStats
com.android.server.LocalServices
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index f9196f3..d3efa21 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -610,7 +610,7 @@
}
}
- void notifyAccessibilityButtonClicked(int displayId) {
+ void notifyMagnificationShortcutTriggered(int displayId) {
if (mMagnificationGestureHandler.size() != 0) {
final MagnificationGestureHandler handler = mMagnificationGestureHandler.get(displayId);
if (handler != null) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 1dc3fb4..36d97f6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -2220,10 +2220,10 @@
}
}
- private void sendAccessibilityButtonToInputFilter(int displayId) {
+ private void notifyMagnificationShortcutTriggered(int displayId) {
synchronized (mLock) {
if (mHasInputFilter && mInputFilter != null) {
- mInputFilter.notifyAccessibilityButtonClicked(displayId);
+ mInputFilter.notifyMagnificationShortcutTriggered(displayId);
}
}
}
@@ -3898,7 +3898,7 @@
.isActivated(displayId);
logAccessibilityShortcutActivated(mContext, MAGNIFICATION_COMPONENT_NAME, shortcutType,
enabled);
- sendAccessibilityButtonToInputFilter(displayId);
+ notifyMagnificationShortcutTriggered(displayId);
return;
}
final ComponentName targetComponentName = ComponentName.unflattenFromString(targetName);
diff --git a/services/autofill/java/com/android/server/autofill/RequestId.java b/services/autofill/java/com/android/server/autofill/RequestId.java
index 29ad786..d8069a8 100644
--- a/services/autofill/java/com/android/server/autofill/RequestId.java
+++ b/services/autofill/java/com/android/server/autofill/RequestId.java
@@ -16,8 +16,14 @@
package com.android.server.autofill;
-import java.util.List;
+import static com.android.server.autofill.Helper.sDebug;
+
+import android.util.Slog;
+import android.util.SparseArray;
+
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.List;
+import java.util.Random;
// Helper class containing various methods to deal with FillRequest Ids.
// For authentication flows, there needs to be a way to know whether to retrieve the Fill
@@ -25,56 +31,97 @@
// way to achieve this is by assigning odd number request ids to secondary provider and
// even numbers to primary provider.
public class RequestId {
+ private AtomicInteger sIdCounter;
- private AtomicInteger sIdCounter;
+ // The minimum request id is 2 to avoid possible authentication issues.
+ static final int MIN_REQUEST_ID = 2;
+ // The maximum request id is 0x7FFF to make sure the 16th bit is 0.
+ // This is to make sure the authentication id is always positive.
+ static final int MAX_REQUEST_ID = 0x7FFF; // 32767
- // Mainly used for tests
- RequestId(int start) {
- sIdCounter = new AtomicInteger(start);
- }
+ // The maximum start id is made small to best avoid wrapping around.
+ static final int MAX_START_ID = 1000;
+ // The magic number is used to determine if a wrap has happened.
+ // The underlying assumption of MAGIC_NUMBER is that there can't be as many as MAGIC_NUMBER
+ // of fill requests in one session. so there can't be as many as MAGIC_NUMBER of fill requests
+ // getting dropped.
+ static final int MAGIC_NUMBER = 5000;
- public RequestId() {
- this((int) (Math.floor(Math.random() * 0xFFFF)));
- }
+ static final int MIN_PRIMARY_REQUEST_ID = 2;
+ static final int MAX_PRIMARY_REQUEST_ID = 0x7FFE; // 32766
- public static int getLastRequestIdIndex(List<Integer> requestIds) {
- int lastId = -1;
- int indexOfBiggest = -1;
- // Biggest number is usually the latest request, since IDs only increase
- // The only exception is when the request ID wraps around back to 0
- for (int i = requestIds.size() - 1; i >= 0; i--) {
- if (requestIds.get(i) > lastId) {
- lastId = requestIds.get(i);
- indexOfBiggest = i;
- }
- }
+ static final int MIN_SECONDARY_REQUEST_ID = 3;
+ static final int MAX_SECONDARY_REQUEST_ID = 0x7FFF; // 32767
- // 0xFFFE + 2 == 0x1 (for secondary)
- // 0xFFFD + 2 == 0x0 (for primary)
- // Wrap has occurred
- if (lastId >= 0xFFFD) {
- // Calculate the biggest size possible
- // If list only has one kind of request ids - we need to multiple by 2
- // (since they skip odd ints)
- // Also subtract one from size because at least one integer exists pre-wrap
- int calcSize = (requestIds.size()) * 2;
- //Biggest possible id after wrapping
- int biggestPossible = (lastId + calcSize) % 0xFFFF;
- lastId = -1;
- indexOfBiggest = -1;
- for (int i = 0; i < requestIds.size(); i++) {
- int currentId = requestIds.get(i);
- if (currentId <= biggestPossible && currentId > lastId) {
- lastId = currentId;
- indexOfBiggest = i;
+ private static final String TAG = "RequestId";
+
+ // WARNING: This constructor should only be used for testing
+ RequestId(int startId) {
+ if (startId < MIN_REQUEST_ID || startId > MAX_REQUEST_ID) {
+ throw new IllegalArgumentException("startId must be between " + MIN_REQUEST_ID +
+ " and " + MAX_REQUEST_ID);
}
- }
+ if (sDebug) {
+ Slog.d(TAG, "RequestId(int): startId= " + startId);
+ }
+ sIdCounter = new AtomicInteger(startId);
}
- return indexOfBiggest;
- }
+ // WARNING: This get method should only be used for testing
+ int getRequestId() {
+ return sIdCounter.get();
+ }
- public int nextId(boolean isSecondary) {
+ public RequestId() {
+ Random random = new Random();
+ int low = MIN_REQUEST_ID;
+ int high = MAX_START_ID + 1; // nextInt is exclusive on upper limit
+
+ // Generate a random start request id that >= MIN_REQUEST_ID and <= MAX_START_ID
+ int startId = random.nextInt(high - low) + low;
+ if (sDebug) {
+ Slog.d(TAG, "RequestId(): startId= " + startId);
+ }
+ sIdCounter = new AtomicInteger(startId);
+ }
+
+ // Given a list of request ids, find the index of the last request id.
+ // Note: Since the request id wraps around, the largest request id may not be
+ // the latest request id.
+ //
+ // @param requestIds List of request ids in ascending order with at least one element.
+ // @return Index of the last request id.
+ public static int getLastRequestIdIndex(List<Integer> requestIds) {
+ // If there is only one request id, return index as 0.
+ if (requestIds.size() == 1) {
+ return 0;
+ }
+
+ // We have to use a magical number to determine if a wrap has happened because
+ // the request id could be lost. The underlying assumption of MAGIC_NUMBER is that
+ // there can't be as many as MAGIC_NUMBER of fill requests in one session.
+ boolean wrapHasHappened = false;
+ int latestRequestIdIndex = -1;
+
+ for (int i = 0; i < requestIds.size() - 1; i++) {
+ if (requestIds.get(i+1) - requestIds.get(i) > MAGIC_NUMBER) {
+ wrapHasHappened = true;
+ latestRequestIdIndex = i;
+ break;
+ }
+ }
+
+ // If there was no wrap, the last request index is the last index.
+ if (!wrapHasHappened) {
+ latestRequestIdIndex = requestIds.size() - 1;
+ }
+ if (sDebug) {
+ Slog.d(TAG, "getLastRequestIdIndex(): latestRequestIdIndex = " + latestRequestIdIndex);
+ }
+ return latestRequestIdIndex;
+ }
+
+ public int nextId(boolean isSecondary) {
// For authentication flows, there needs to be a way to know whether to retrieve the Fill
// Response from the primary provider or the secondary provider from the requestId. A simple
// way to achieve this is by assigning odd number request ids to secondary provider and
@@ -82,13 +129,20 @@
int requestId;
do {
- requestId = sIdCounter.incrementAndGet() % 0xFFFF;
+ requestId = sIdCounter.incrementAndGet() % (MAX_REQUEST_ID + 1);
+ // Skip numbers smaller than MIN_REQUEST_ID to avoid possible authentication issue
+ if (requestId < MIN_REQUEST_ID) {
+ requestId = MIN_REQUEST_ID;
+ }
sIdCounter.set(requestId);
} while (isSecondaryProvider(requestId) != isSecondary);
+ if (sDebug) {
+ Slog.d(TAG, "nextId(): requestId = " + requestId);
+ }
return requestId;
- }
+ }
- public static boolean isSecondaryProvider(int requestId) {
- return requestId % 2 == 1;
- }
+ public static boolean isSecondaryProvider(int requestId) {
+ return requestId % 2 == 1;
+ }
}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 494e956..c6ddc16 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -6902,17 +6902,18 @@
return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
}
+ // Return latest response index in mResponses SparseArray.
@GuardedBy("mLock")
private int getLastResponseIndexLocked() {
- if (mResponses != null) {
- List<Integer> requestIdList = new ArrayList<>();
- final int responseCount = mResponses.size();
- for (int i = 0; i < responseCount; i++) {
- requestIdList.add(mResponses.keyAt(i));
- }
- return mRequestId.getLastRequestIdIndex(requestIdList);
+ if (mResponses == null || mResponses.size() == 0) {
+ return -1;
}
- return -1;
+ List<Integer> requestIdList = new ArrayList<>();
+ final int responseCount = mResponses.size();
+ for (int i = 0; i < responseCount; i++) {
+ requestIdList.add(mResponses.keyAt(i));
+ }
+ return mRequestId.getLastRequestIdIndex(requestIdList);
}
private LogMaker newLogMaker(int category) {
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index afeafa4..988a213 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -87,15 +87,6 @@
void onSecureWindowShown(int displayId, int uid);
}
- /**
- * For communicating when activities are blocked from entering PIP on the display by this
- * policy controller.
- */
- public interface PipBlockedCallback {
- /** Called when an activity is blocked from entering PIP. */
- void onEnteringPipBlocked(int uid);
- }
-
/** Interface to listen for interception of intents. */
public interface IntentListenerCallback {
/** Returns true when an intent should be intercepted */
@@ -136,7 +127,6 @@
@GuardedBy("mGenericWindowPolicyControllerLock")
private final ArraySet<Integer> mRunningUids = new ArraySet<>();
@Nullable private final ActivityListener mActivityListener;
- @Nullable private final PipBlockedCallback mPipBlockedCallback;
@Nullable private final IntentListenerCallback mIntentListenerCallback;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@NonNull
@@ -190,7 +180,6 @@
@NonNull Set<ComponentName> crossTaskNavigationExemptions,
@Nullable ComponentName permissionDialogComponent,
@Nullable ActivityListener activityListener,
- @Nullable PipBlockedCallback pipBlockedCallback,
@Nullable ActivityBlockedCallback activityBlockedCallback,
@Nullable SecureWindowCallback secureWindowCallback,
@Nullable IntentListenerCallback intentListenerCallback,
@@ -208,7 +197,6 @@
mActivityBlockedCallback = activityBlockedCallback;
setInterestedWindowFlags(windowFlags, systemWindowFlags);
mActivityListener = activityListener;
- mPipBlockedCallback = pipBlockedCallback;
mSecureWindowCallback = secureWindowCallback;
mIntentListenerCallback = intentListenerCallback;
mDisplayCategories = displayCategories;
@@ -346,6 +334,10 @@
}
final UserHandle activityUser =
UserHandle.getUserHandleForUid(activityInfo.applicationInfo.uid);
+ if (!activityUser.isSystem() && !mAllowedUsers.contains(activityUser)) {
+ logActivityLaunchBlocked("Activity launch disallowed from user " + activityUser);
+ return false;
+ }
final ComponentName activityComponent = activityInfo.getComponentName();
if (BLOCKED_APP_STREAMING_COMPONENT.equals(activityComponent) && activityUser.isSystem()) {
// The error dialog alerting users that streaming is blocked is always allowed.
@@ -464,18 +456,6 @@
return mShowTasksInHostDeviceRecents;
}
}
-
- @Override
- public boolean isEnteringPipAllowed(int uid) {
- if (super.isEnteringPipAllowed(uid)) {
- return true;
- }
- if (mPipBlockedCallback != null) {
- mHandler.post(() -> mPipBlockedCallback.onEnteringPipBlocked(uid));
- }
- return false;
- }
-
@Override
public @Nullable ComponentName getCustomHomeComponent() {
return mCustomHomeComponent;
@@ -512,7 +492,6 @@
"virtual_devices.value_activity_blocked_count",
mAttributionSource.getUid());
}
-
}
private static boolean isAllowedByPolicy(boolean allowedByDefault,
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..3c323f9 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,9 @@
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 ActivityListener mActivityListenerAdapter = null;
+ private IVirtualDeviceSoundEffectListener mSoundEffectListener;
private final DisplayManagerGlobal mDisplayManager;
private final DisplayManagerInternal mDisplayManagerInternal;
@GuardedBy("mVirtualDeviceLock")
@@ -239,6 +240,16 @@
Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
}
}
+
+ @Override
+ public void onActivityLaunchBlocked(int displayId,
+ @NonNull ComponentName componentName, @UserIdInt int userId) {
+ try {
+ mActivityListener.onActivityLaunchBlocked(displayId, componentName, userId);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to call mActivityListener for display: " + displayId, e);
+ }
+ }
};
}
@@ -303,7 +314,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 +418,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 +433,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 +485,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 +1171,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);
@@ -1189,6 +1220,10 @@
final ComponentName homeComponent =
Flags.vdmCustomHome() ? mParams.getHomeComponent() : null;
+ if (mActivityListenerAdapter == null) {
+ mActivityListenerAdapter = createListenerAdapter();
+ }
+
final GenericWindowPolicyController gwpc = new GenericWindowPolicyController(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
@@ -1201,8 +1236,7 @@
? mParams.getBlockedCrossTaskNavigations()
: mParams.getAllowedCrossTaskNavigations(),
mPermissionDialogComponent,
- createListenerAdapter(),
- this::onEnteringPipBlocked,
+ mActivityListenerAdapter,
this::onActivityBlocked,
this::onSecureWindowShown,
this::shouldInterceptIntent,
@@ -1286,12 +1320,22 @@
@RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
private void onActivityBlocked(int displayId, ActivityInfo activityInfo) {
- Intent intent = BlockedAppStreamingActivity.createIntent(
- activityInfo, mAssociationInfo.getDisplayName());
- mContext.startActivityAsUser(
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
- ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
- UserHandle.SYSTEM);
+ Intent intent = BlockedAppStreamingActivity.createIntent(activityInfo, getDisplayName());
+ if (!android.companion.virtualdevice.flags.Flags.activityControlApi()
+ || !Objects.equals(activityInfo.getComponentName(), intent.getComponent())) {
+ mContext.startActivityAsUser(
+ intent.addFlags(
+ Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK),
+ ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle(),
+ UserHandle.SYSTEM);
+ }
+ if (android.companion.virtualdevice.flags.Flags.activityControlApi()) {
+ mActivityListenerAdapter.onActivityLaunchBlocked(
+ displayId,
+ activityInfo.getComponentName(),
+ UserHandle.getUserHandleForUid(
+ activityInfo.applicationInfo.uid).getIdentifier());
+ }
}
private void onSecureWindowShown(int displayId, int uid) {
@@ -1374,7 +1418,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;
@@ -1491,12 +1535,6 @@
return mInputController.getInputDeviceDescriptors().values().stream().anyMatch(
inputDeviceDescriptor -> inputDeviceDescriptor.getInputDeviceId() == inputDeviceId);
}
-
- void onEnteringPipBlocked(int uid) {
- // Do nothing. ActivityRecord#checkEnterPictureInPictureState logs that the display does not
- // support PiP.
- }
-
void playSoundEffect(int effectType) {
try {
mSoundEffectListener.onPlaySoundEffect(effectType);
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/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b23f5f2..f6b3b39 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5481,8 +5481,13 @@
private boolean isHomeLaunchDelayable() {
// This feature is disabled on Auto since it seems to add an unacceptably long boot delay
// without even solving the underlying issue (it merely hits the timeout).
- return enableHomeDelay() &&
- !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
+ // This feature is disabled on TV since the ThemeOverlayController is currently not present
+ // and therefore we do not want to wait unnecessarily.
+ // This feature is currently disabled in WearOS to avoid extreme boot regressions
+ return enableHomeDelay()
+ && !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+ && !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+ && !mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
}
final void ensureBootCompleted() {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 0c14a1c..00183ac 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -848,6 +848,7 @@
}
private void syncStats(String reason, int flags) {
+ mStats.collectPowerStatsSamples();
awaitUninterruptibly(mWorker.scheduleSync(reason, flags));
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c094724..f4b1229 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -855,8 +855,8 @@
Slog.i(TAG, "Failed to connect to lmkd, retry after " +
LMKD_RECONNECT_DELAY_MS + " ms");
// retry after LMKD_RECONNECT_DELAY_MS
- sKillHandler.sendMessageDelayed(sKillHandler.obtainMessage(
- KillHandler.LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS);
+ sendMessageDelayed(obtainMessage(
+ LMKD_RECONNECT_MSG), LMKD_RECONNECT_DELAY_MS);
}
break;
default:
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 27fda15..cc4f7d9 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -51,6 +51,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
+import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -374,6 +375,16 @@
deviceInfo.mScoAudioMode, deviceInfo.mIsPrivileged, deviceInfo.mEventSource);
}
+ /**
+ * Indicates if a Bluetooth SCO activation request owner is controlling
+ * the SCO audio state itself or not.
+ * @param uid the UI of the SOC request owner app
+ * @return true if we should control SCO audio state, false otherwise
+ */
+ private boolean shouldStartScoForUid(int uid) {
+ return !(uid == Process.BLUETOOTH_UID || uid == Process.PHONE_UID);
+ }
+
@GuardedBy("mDeviceStateLock")
/*package*/ void setCommunicationRouteForClient(
IBinder cb, int uid, AudioDeviceAttributes device,
@@ -388,7 +399,7 @@
+ " device: " + device + " isPrivileged: " + isPrivileged
+ " from API: " + eventSource)).printLog(TAG));
- final boolean wasBtScoRequested = isBluetoothScoRequested();
+ final int previousBtScoRequesterUid = bluetoothScoRequestOwnerUid();
CommunicationRouteClient client;
// Save previous client route in case of failure to start BT SCO audio
@@ -412,8 +423,40 @@
if (client == null) {
return;
}
- if (!mScoManagedByAudio) {
- boolean isBtScoRequested = isBluetoothScoRequested();
+ final int btScoRequesterUid = bluetoothScoRequestOwnerUid();
+ final boolean isBtScoRequested = btScoRequesterUid != -1;
+ final boolean wasBtScoRequested = previousBtScoRequesterUid != -1;
+
+ if (mScoManagedByAudio) {
+ if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive()
+ || !mBtHelper.isBluetoothScoRequestedInternally())) {
+ boolean scoStarted = false;
+ if (shouldStartScoForUid(btScoRequesterUid)) {
+ scoStarted = mBtHelper.startBluetoothSco(scoAudioMode, eventSource);
+ if (!scoStarted) {
+ Log.w(TAG, "setCommunicationRouteForClient: "
+ + "failure to start BT SCO for uid: " + uid);
+ // clean up or restore previous client selection
+ if (prevClientDevice != null) {
+ addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
+ } else {
+ removeCommunicationRouteClient(cb, true);
+ }
+ postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ }
+ } else {
+ scoStarted = true;
+ }
+ if (scoStarted) {
+ setBluetoothScoOn(true, "setCommunicationRouteForClient");
+ }
+ } else if (!isBtScoRequested && wasBtScoRequested) {
+ if (shouldStartScoForUid(previousBtScoRequesterUid)) {
+ mBtHelper.stopBluetoothSco(eventSource);
+ }
+ setBluetoothScoOn(false, "setCommunicationRouteForClient");
+ }
+ } else {
if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive()
|| !mBtHelper.isBluetoothScoRequestedInternally())) {
if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
@@ -575,12 +618,12 @@
@GuardedBy("mDeviceStateLock")
/*package*/ void updateCommunicationRouteClientState(
CommunicationRouteClient client, boolean wasActive) {
- boolean wasBtScoRequested = isBluetoothScoRequested();
+ int btScoRequesterUid = bluetoothScoRequestOwnerUid();
client.setPlaybackActive(mAudioService.isPlaybackActiveForUid(client.getUid()));
client.setRecordingActive(mAudioService.isRecordingActiveForUid(client.getUid()));
if (wasActive != client.isActive()) {
postUpdateCommunicationRouteClient(
- wasBtScoRequested, "updateCommunicationRouteClientState");
+ btScoRequesterUid, "updateCommunicationRouteClientState");
}
}
@@ -763,6 +806,22 @@
}
/**
+ * Helper method on top of isBluetoothScoRequested() returning the UID of the
+ * BT SCO route request owner of -1 if SCO is not requested.
+ * @return the UID of the BT SCO route request owner of -1 if SCO is not requested.
+ */
+ @GuardedBy("mDeviceStateLock")
+ /*package*/ int bluetoothScoRequestOwnerUid() {
+ if (!isBluetoothScoRequested()) {
+ return -1;
+ }
+ CommunicationRouteClient crc = topCommunicationRouteClient();
+ if (crc == null) {
+ return -1;
+ }
+ return crc.getUid();
+ }
+ /**
* Helper method on top of isDeviceRequestedForCommunication() indicating if
* Bluetooth LE Audio communication device is currently requested or not.
* @return true if Bluetooth LE Audio device is requested, false otherwise.
@@ -1148,15 +1207,18 @@
}
}
+ @GuardedBy("mDeviceStateLock")
/*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
synchronized (mBluetoothAudioStateLock) {
- boolean isBtScoRequested = isBluetoothScoRequested();
+ int btScoRequesterUId = bluetoothScoRequestOwnerUid();
Log.i(TAG, "setBluetoothScoOn: " + on + ", mBluetoothScoOn: "
- + mBluetoothScoOn + ", isBtScoRequested: " + isBtScoRequested
+ + mBluetoothScoOn + ", btScoRequesterUId: " + btScoRequesterUId
+ ", from: " + eventSource);
mBluetoothScoOn = on;
updateAudioHalBluetoothState();
- postUpdateCommunicationRouteClient(isBtScoRequested, eventSource);
+ if (!mScoManagedByAudio) {
+ postUpdateCommunicationRouteClient(btScoRequesterUId, eventSource);
+ }
}
}
@@ -1510,9 +1572,9 @@
}
/*package*/ void postUpdateCommunicationRouteClient(
- boolean wasBtScoRequested, String eventSource) {
+ int btScoRequesterUid, String eventSource) {
sendILMsgNoDelay(MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE,
- wasBtScoRequested ? 1 : 0, eventSource);
+ btScoRequesterUid, eventSource);
}
/*package*/ void postSetCommunicationDeviceForClient(CommunicationDeviceInfo info) {
@@ -1865,7 +1927,7 @@
|| btInfo.mProfile == BluetoothProfile.HEARING_AID
|| (mScoManagedByAudio
&& btInfo.mProfile == BluetoothProfile.HEADSET)) {
- onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
+ onUpdateCommunicationRouteClient(bluetoothScoRequestOwnerUid(),
"setBluetoothActiveDevice");
}
}
@@ -1927,11 +1989,11 @@
case MSG_I_SET_MODE_OWNER:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- boolean wasBtScoRequested = isBluetoothScoRequested();
+ int btScoRequesterUid = bluetoothScoRequestOwnerUid();
mAudioModeOwner = (AudioModeInfo) msg.obj;
if (mAudioModeOwner.mMode != AudioSystem.MODE_RINGTONE) {
onUpdateCommunicationRouteClient(
- wasBtScoRequested, "setNewModeOwner");
+ btScoRequesterUid, "setNewModeOwner");
}
}
}
@@ -1958,7 +2020,7 @@
case MSG_IL_UPDATE_COMMUNICATION_ROUTE_CLIENT:
synchronized (mSetModeLock) {
synchronized (mDeviceStateLock) {
- onUpdateCommunicationRouteClient(msg.arg1 == 1, (String) msg.obj);
+ onUpdateCommunicationRouteClient(msg.arg1, (String) msg.obj);
}
}
break;
@@ -2457,7 +2519,7 @@
@Nullable private AudioDeviceAttributes preferredCommunicationDevice() {
boolean btSCoOn = mBtHelper.isBluetoothScoOn();
synchronized (mBluetoothAudioStateLock) {
- btSCoOn = btSCoOn && mBluetoothScoOn;
+ btSCoOn = (btSCoOn || mScoManagedByAudio) && mBluetoothScoOn;
}
if (btSCoOn) {
@@ -2522,18 +2584,28 @@
*/
// @GuardedBy("mSetModeLock")
@GuardedBy("mDeviceStateLock")
- private void onUpdateCommunicationRouteClient(boolean wasBtScoRequested, String eventSource) {
+ private void onUpdateCommunicationRouteClient(
+ int previousBtScoRequesterUid, String eventSource) {
CommunicationRouteClient crc = topCommunicationRouteClient();
if (AudioService.DEBUG_COMM_RTE) {
Log.v(TAG, "onUpdateCommunicationRouteClient, crc: " + crc
- + " wasBtScoRequested: " + wasBtScoRequested + " eventSource: " + eventSource);
+ + " previousBtScoRequesterUid: " + previousBtScoRequesterUid
+ + " eventSource: " + eventSource);
}
if (crc != null) {
setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
} else {
- if (!mScoManagedByAudio && !isBluetoothScoRequested() && wasBtScoRequested) {
- mBtHelper.stopBluetoothSco(eventSource);
+ boolean wasScoRequested = previousBtScoRequesterUid != -1;
+ if (!isBluetoothScoRequested() && wasScoRequested) {
+ if (mScoManagedByAudio) {
+ if (shouldStartScoForUid(previousBtScoRequesterUid)) {
+ mBtHelper.stopBluetoothSco(eventSource);
+ }
+ setBluetoothScoOn(false, eventSource);
+ } else {
+ mBtHelper.stopBluetoothSco(eventSource);
+ }
}
updateCommunicationRoute(eventSource);
}
@@ -2793,12 +2865,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/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
index 14eae8d..c5180af 100644
--- a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
+++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
@@ -36,6 +36,7 @@
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IntArray;
+import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.media.permission.INativePermissionController;
@@ -61,6 +62,9 @@
static final String[] MONITORED_PERMS = new String[PermissionEnum.ENUM_SIZE];
+ static final byte[] HDS_PERMS = new byte[] {PermissionEnum.CAPTURE_AUDIO_HOTWORD,
+ PermissionEnum.CAPTURE_AUDIO_OUTPUT, PermissionEnum.RECORD_AUDIO};
+
static {
MONITORED_PERMS[PermissionEnum.RECORD_AUDIO] = RECORD_AUDIO;
MONITORED_PERMS[PermissionEnum.MODIFY_AUDIO_ROUTING] = MODIFY_AUDIO_ROUTING;
@@ -88,6 +92,7 @@
@GuardedBy("mLock")
private final Map<Integer, Set<String>> mPackageMap;
+
// Values are sorted
@GuardedBy("mLock")
private final int[][] mPermMap = new int[PermissionEnum.ENUM_SIZE][];
@@ -95,6 +100,9 @@
@GuardedBy("mLock")
private boolean mIsUpdateDeferred = true;
+ @GuardedBy("mLock")
+ private int mHdsUid = -1;
+
/**
* @param appInfos - PackageState for all apps on the device, used to populate init state
*/
@@ -124,7 +132,7 @@
try {
for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) {
if (mIsUpdateDeferred) {
- mPermMap[i] = getUidsHoldingPerm(MONITORED_PERMS[i]);
+ mPermMap[i] = getUidsHoldingPerm(i);
}
mDest.populatePermissionState(i, mPermMap[i]);
}
@@ -184,7 +192,7 @@
}
try {
for (byte i = 0; i < PermissionEnum.ENUM_SIZE; i++) {
- var newPerms = getUidsHoldingPerm(MONITORED_PERMS[i]);
+ var newPerms = getUidsHoldingPerm(i);
if (!Arrays.equals(newPerms, mPermMap[i])) {
mPermMap[i] = newPerms;
mDest.populatePermissionState(i, newPerms);
@@ -199,6 +207,77 @@
}
}
+ public void setIsolatedServiceUid(int uid, int owningUid) {
+ synchronized (mLock) {
+ if (mHdsUid == uid) return;
+ var packageNameSet = mPackageMap.get(owningUid);
+ if (packageNameSet == null) return;
+ var packageName = packageNameSet.iterator().next();
+ onModifyPackageState(uid, packageName, /* isRemove= */ false);
+ // permissions
+ mHdsUid = uid;
+ if (mDest == null) {
+ mIsUpdateDeferred = true;
+ return;
+ }
+ try {
+ for (byte perm : HDS_PERMS) {
+ int[] newPerms = new int[mPermMap[perm].length + 1];
+ System.arraycopy(mPermMap[perm], 0, newPerms, 0, mPermMap[perm].length);
+ newPerms[newPerms.length - 1] = mHdsUid;
+ Arrays.sort(newPerms);
+ mPermMap[perm] = newPerms;
+ mDest.populatePermissionState(perm, newPerms);
+ }
+ } catch (RemoteException e) {
+ // We will re-init the state when the service comes back up
+ mDest = null;
+ // We didn't necessarily finish
+ mIsUpdateDeferred = true;
+ }
+ }
+ }
+
+ public void clearIsolatedServiceUid(int uid) {
+ synchronized (mLock) {
+ if (mHdsUid != uid) return;
+ var packageNameSet = mPackageMap.get(uid);
+ if (packageNameSet == null) return;
+ var packageName = packageNameSet.iterator().next();
+ onModifyPackageState(uid, packageName, /* isRemove= */ true);
+ // permissions
+ if (mDest == null) {
+ mIsUpdateDeferred = true;
+ return;
+ }
+ try {
+ for (byte perm : HDS_PERMS) {
+ int[] newPerms = new int[mPermMap[perm].length - 1];
+ int ind = Arrays.binarySearch(mPermMap[perm], uid);
+ if (ind < 0) continue;
+ System.arraycopy(mPermMap[perm], 0, newPerms, 0, ind);
+ System.arraycopy(mPermMap[perm], ind + 1, newPerms, ind,
+ mPermMap[perm].length - ind - 1);
+ mPermMap[perm] = newPerms;
+ mDest.populatePermissionState(perm, newPerms);
+ }
+ } catch (RemoteException e) {
+ // We will re-init the state when the service comes back up
+ mDest = null;
+ // We didn't necessarily finish
+ mIsUpdateDeferred = true;
+ }
+ mHdsUid = -1;
+ }
+ }
+
+ private boolean isSpecialHdsPermission(int perm) {
+ for (var hdsPerm : HDS_PERMS) {
+ if (perm == hdsPerm) return true;
+ }
+ return false;
+ }
+
/** Called when full syncing package state to audioserver. */
@GuardedBy("mLock")
private void resetNativePackageState() {
@@ -223,16 +302,19 @@
@GuardedBy("mLock")
/** Return all uids (not app-ids) which currently hold a given permission. Not app-op aware */
- private int[] getUidsHoldingPerm(String perm) {
+ private int[] getUidsHoldingPerm(int perm) {
IntArray acc = new IntArray();
for (int userId : mUserIdSupplier.get()) {
for (int appId : mPackageMap.keySet()) {
int uid = UserHandle.getUid(userId, appId);
- if (mPermissionPredicate.test(uid, perm)) {
+ if (mPermissionPredicate.test(uid, MONITORED_PERMS[perm])) {
acc.add(uid);
}
}
}
+ if (isSpecialHdsPermission(perm) && mHdsUid != -1) {
+ acc.add(mHdsUid);
+ }
var unwrapped = acc.toArray();
Arrays.sort(unwrapped);
return unwrapped;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index c89992d..39cb5a9 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(),
@@ -11963,8 +11967,9 @@
var umi = LocalServices.getService(UserManagerInternal.class);
var pmsi = LocalServices.getService(PermissionManagerServiceInternal.class);
var provider = new AudioServerPermissionProvider(packageStates,
- (Integer uid, String perm) -> (pmsi.checkUidPermission(uid, perm,
- Context.DEVICE_ID_DEFAULT) == PackageManager.PERMISSION_GRANTED),
+ (Integer uid, String perm) -> ActivityManager.checkComponentPermission(perm, uid,
+ /* owningUid = */ -1, /* exported */true)
+ == PackageManager.PERMISSION_GRANTED,
() -> umi.getUserIds()
);
audioPolicy.registerOnStartTask(() -> {
@@ -12326,13 +12331,19 @@
}
@Override
- public void addAssistantServiceUid(int uid) {
+ public void addAssistantServiceUid(int uid, int owningUid) {
+ if (audioserverPermissions()) {
+ mPermissionProvider.setIsolatedServiceUid(uid, owningUid);
+ }
sendMsg(mAudioHandler, MSG_ADD_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE,
uid, 0, null, 0);
}
@Override
public void removeAssistantServiceUid(int uid) {
+ if (audioserverPermissions()) {
+ mPermissionProvider.clearIsolatedServiceUid(uid);
+ }
sendMsg(mAudioHandler, MSG_REMOVE_ASSISTANT_SERVICE_UID, SENDMSG_QUEUE,
uid, 0, null, 0);
}
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 8008717..0de3428 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -142,7 +142,6 @@
private static final int SCO_MODE_MAX = 2;
private static final int BT_HEARING_AID_GAIN_MIN = -128;
- private static final int BT_LE_AUDIO_MIN_VOL = 0;
private static final int BT_LE_AUDIO_MAX_VOL = 255;
// BtDevice constants currently rolling out under flag protection. Use own
@@ -211,8 +210,7 @@
//----------------------------------------------------------------------
// Interface for AudioDeviceBroker
- // @GuardedBy("mDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
+ // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
/*package*/ synchronized void onSystemReady() {
mScoConnectionState = android.media.AudioManager.SCO_AUDIO_STATE_ERROR;
resetBluetoothSco();
@@ -373,8 +371,7 @@
return codecAndChanged;
}
- // @GuardedBy("mDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
+ // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
/*package*/ synchronized void onReceiveBtEvent(Intent intent) {
final String action = intent.getAction();
@@ -396,78 +393,67 @@
}
/**
- * Exclusively called from AudioDeviceBroker when handling MSG_L_RECEIVED_BT_EVENT
+ * Exclusively called from AudioDeviceBroker (with mSetModeLock held)
+ * when handling MSG_L_RECEIVED_BT_EVENT in {@link #onReceiveBtEvent(Intent)}
* as part of the serialization of the communication route selection
*/
- // @GuardedBy("mDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
+ @GuardedBy("BtHelper.this")
private void onScoAudioStateChanged(int state) {
boolean broadcast = false;
int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
- Log.i(TAG, "onScoAudioStateChanged state: " + state + " mScoAudioState: " + mScoAudioState);
- if (mDeviceBroker.isScoManagedByAudio()) {
- switch (state) {
- case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged");
- scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+ Log.i(TAG, "onScoAudioStateChanged state: " + state
+ + ", mScoAudioState: " + mScoAudioState);
+ switch (state) {
+ case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+ && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ } else if (mDeviceBroker.isBluetoothScoRequested()) {
+ // broadcast intent if the connection was initated by AudioService
broadcast = true;
- break;
- case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
- mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged");
- scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
- broadcast = true;
- break;
- default:
- break;
- }
- } else {
- switch (state) {
- case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
- && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- } else if (mDeviceBroker.isBluetoothScoRequested()) {
- // broadcast intent if the connection was initated by AudioService
- broadcast = true;
- }
+ }
+ if (!mDeviceBroker.isScoManagedByAudio()) {
mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged");
- break;
- case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+ }
+ break;
+ case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+ if (!mDeviceBroker.isScoManagedByAudio()) {
mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged");
- scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
- // There are two cases where we want to immediately reconnect audio:
- // 1) If a new start request was received while disconnecting: this was
- // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
- // 2) If audio was connected then disconnected via Bluetooth APIs and
- // we still have pending activation requests by apps: this is indicated by
- // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested.
- if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
- if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
- && connectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode)) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
- broadcast = true;
- break;
- }
- }
- if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
+ }
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+ // There are two cases where we want to immediately reconnect audio:
+ // 1) If a new start request was received while disconnecting: this was
+ // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
+ // 2) If audio was connected then disconnected via Bluetooth APIs and
+ // we still have pending activation requests by apps: this is indicated by
+ // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested.
+ if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+ if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
+ && connectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
broadcast = true;
+ break;
}
- mScoAudioState = SCO_STATE_INACTIVE;
- break;
- case BluetoothHeadset.STATE_AUDIO_CONNECTING:
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
- && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- break;
- default:
- break;
- }
+ }
+ if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
+ broadcast = true;
+ }
+ mScoAudioState = SCO_STATE_INACTIVE;
+ break;
+ case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+ && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ break;
+ default:
+ break;
}
if (broadcast) {
+ Log.i(TAG, "onScoAudioStateChanged broadcasting state: " + scoAudioState);
broadcastScoConnectionState(scoAudioState);
//FIXME: this is to maintain compatibility with deprecated intent
// AudioManager.ACTION_SCO_AUDIO_STATE_CHANGED. Remove when appropriate.
@@ -493,16 +479,14 @@
|| mScoAudioState == SCO_STATE_ACTIVATE_REQ;
}
- // @GuardedBy("mDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
+ // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
/*package*/ synchronized boolean startBluetoothSco(int scoAudioMode,
@NonNull String eventSource) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
}
- // @GuardedBy("mDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
+ // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
/*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
@@ -574,8 +558,7 @@
mScoConnectionState = state;
}
- // @GuardedBy("mDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
+ // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
/*package*/ synchronized void resetBluetoothSco() {
mScoAudioState = SCO_STATE_INACTIVE;
broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
@@ -584,8 +567,7 @@
mDeviceBroker.setBluetoothScoOn(false, "resetBluetoothSco");
}
- // @GuardedBy("mDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
+ // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
/*package*/ synchronized void onBtProfileDisconnected(int profile) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"BT profile " + BluetoothProfile.getProfileName(profile)
@@ -649,8 +631,7 @@
MyLeAudioCallback mLeAudioCallback = null;
- // @GuardedBy("mDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
+ // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
/*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) {
AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
"BT profile " + BluetoothProfile.getProfileName(profile) + " connected to proxy "
@@ -787,8 +768,7 @@
}
}
- // @GuardedBy("mDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
+ // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
private void onHeadsetProfileConnected(@NonNull BluetoothHeadset headset) {
// Discard timeout message
mDeviceBroker.handleCancelFailureToConnectToBtHeadsetService();
@@ -931,8 +911,7 @@
return btDevice == null ? "(null)" : btDevice.getAnonymizedAddress();
}
- // @GuardedBy("mDeviceBroker.mSetModeLock")
- @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock")
+ // Called locked by ADeviceBroker.mSetModeLock -> AudioDeviceBroker.mDeviceStateLock
/*package */ synchronized void onSetBtScoActiveDevice(BluetoothDevice btDevice) {
Log.i(TAG, "onSetBtScoActiveDevice: " + getAnonymizedAddress(mBluetoothHeadsetDevice)
+ " -> " + getAnonymizedAddress(btDevice));
@@ -1133,6 +1112,9 @@
//-----------------------------------------------------
// Utilities
+
+ // suppress warning due to generic Intent passed as param
+ @SuppressWarnings("AndroidFrameworkRequiresPermission")
private void sendStickyBroadcastToAll(Intent intent) {
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
final long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 5c74304..ded93e6 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -643,9 +643,9 @@
if (index > safeIndex) {
streamState.setIndex(safeIndex, deviceType, caller,
true /*hasModifyAudioSettings*/);
- mAudioHandler.sendMessageAtTime(
+ mAudioHandler.sendMessage(
mAudioHandler.obtainMessage(MSG_SET_DEVICE_VOLUME, deviceType,
- /*arg2=*/0, streamState), /*delay=*/0);
+ /*arg2=*/0, streamState));
}
}
}
@@ -686,8 +686,11 @@
/*package*/ void disableSafeMediaVolume(String callingPackage) {
synchronized (mSafeMediaVolumeStateLock) {
final long identity = Binder.clearCallingIdentity();
- setSafeMediaVolumeEnabled(false, callingPackage);
- Binder.restoreCallingIdentity(identity);
+ try {
+ setSafeMediaVolumeEnabled(false, callingPackage);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
if (mPendingVolumeCommand != null) {
mAudioService.onSetStreamVolume(mPendingVolumeCommand.mStreamType,
@@ -701,6 +704,7 @@
}
}
+ @SuppressWarnings("AndroidFrameworkRequiresPermission")
/*package*/ void scheduleMusicActiveCheck() {
synchronized (mSafeMediaVolumeStateLock) {
cancelMusicActiveCheck();
@@ -1035,10 +1039,9 @@
mSafeMediaVolumeState = SAFE_MEDIA_VOLUME_DISABLED;
}
- mAudioHandler.sendMessageAtTime(
+ mAudioHandler.sendMessage(
mAudioHandler.obtainMessage(MSG_PERSIST_SAFE_VOLUME_STATE,
- persistedState, /*arg2=*/0,
- /*obj=*/null), /*delay=*/0);
+ persistedState, /*arg2=*/0, /*obj=*/null));
}
private void updateCsdEnabled(String caller) {
@@ -1199,8 +1202,8 @@
sanitizeDoseRecords_l();
- mAudioHandler.sendMessageAtTime(mAudioHandler.obtainMessage(MSG_PERSIST_CSD_VALUES,
- /* arg1= */0, /* arg2= */0, /* obj= */null), /* delay= */0);
+ mAudioHandler.sendMessage(mAudioHandler.obtainMessage(MSG_PERSIST_CSD_VALUES,
+ /* arg1= */0, /* arg2= */0, /* obj= */null));
mLogger.enqueue(SoundDoseEvent.getDoseUpdateEvent(currentCsd, totalDuration));
}
@@ -1316,6 +1319,7 @@
}
/** Called when handling MSG_LOWER_VOLUME_TO_RS1 */
+ @SuppressWarnings("AndroidFrameworkRequiresPermission")
private void onLowerVolumeToRs1() {
final ArrayList<AudioDeviceAttributes> devices = mAudioService.getDevicesForAttributesInt(
new AudioAttributes.Builder().setUsage(
@@ -1360,9 +1364,9 @@
@Override
public String toString() {
- return new StringBuilder().append("{streamType=").append(mStreamType).append(",index=")
- .append(mIndex).append(",flags=").append(mFlags).append(",device=")
- .append(mDevice).append('}').toString();
+ return "{streamType=" + mStreamType
+ + ",index=" + mIndex + ",flags=" + mFlags
+ + ",device=" + mDevice + "}";
}
}
}
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/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 2a16872..f5a2a21 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -369,6 +369,10 @@
checkPermission();
}
+ if ((authenticators & Authenticators.MANDATORY_BIOMETRICS) != 0) {
+ checkBiometricAdvancedPermission();
+ }
+
final long identity = Binder.clearCallingIdentity();
try {
final int result = mBiometricService.canAuthenticate(
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index daaafcb..693a3e6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -418,7 +418,7 @@
}
protected int getRequestReason() {
- if (isKeyguard()) {
+ if (isKeyguard() && !isBiometricPrompt()) {
return BiometricRequestConstants.REASON_AUTH_KEYGUARD;
} else if (isBiometricPrompt()) {
// BP reason always takes precedent over settings, since callers from within
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/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index e8394d4..619aecf 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -19,6 +19,9 @@
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FEATURE_REAR_DISPLAY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY;
+import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST;
import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_OVERRIDE_REQUESTS;
import static android.hardware.devicestate.DeviceState.PROPERTY_POLICY_CANCEL_WHEN_REQUESTER_NOT_ON_TOP;
import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE_IDENTIFIER;
@@ -47,6 +50,8 @@
import android.hardware.devicestate.DeviceStateManagerInternal;
import android.hardware.devicestate.IDeviceStateManager;
import android.hardware.devicestate.IDeviceStateManagerCallback;
+import android.hardware.devicestate.feature.flags.FeatureFlags;
+import android.hardware.devicestate.feature.flags.FeatureFlagsImpl;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -175,7 +180,7 @@
private Set<Integer> mDeviceStatesAvailableForAppRequests = new HashSet<>();
- private Set<Integer> mFoldedDeviceStates;
+ private Set<Integer> mFoldedDeviceStates = new HashSet<>();
@Nullable
private DeviceState mRearDisplayState;
@@ -185,6 +190,9 @@
@Nullable
private OverrideRequest mRearDisplayPendingOverrideRequest;
+ @NonNull
+ private final FeatureFlags mFlags;
+
@VisibleForTesting
interface SystemPropertySetter {
void setDebugTracingDeviceStateProperty(String value);
@@ -245,6 +253,7 @@
@NonNull SystemPropertySetter systemPropertySetter) {
super(context);
mSystemPropertySetter = systemPropertySetter;
+ mFlags = new FeatureFlagsImpl();
// We use the DisplayThread because this service indirectly drives
// display (on/off) and window (position) events through its callbacks.
DisplayThread displayThread = DisplayThread.get();
@@ -270,9 +279,12 @@
publishBinderService(Context.DEVICE_STATE_SERVICE, mBinderService);
publishLocalService(DeviceStateManagerInternal.class, new LocalService());
- synchronized (mLock) {
- readStatesAvailableForRequestFromApps();
- mFoldedDeviceStates = readFoldedStates();
+ if (!mFlags.deviceStatePropertyMigration()) {
+ synchronized (mLock) {
+ readStatesAvailableForRequestFromApps();
+ mFoldedDeviceStates = readFoldedStates();
+ setRearDisplayStateLocked();
+ }
}
mActivityTaskManagerInternal.registerScreenObserver(mOverrideRequestScreenObserver);
@@ -461,8 +473,6 @@
mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers, reason);
updatePendingStateLocked();
- setRearDisplayStateLocked();
-
notifyDeviceStateInfoChangedAsync();
mHandler.post(this::notifyPolicyIfNeeded);
@@ -838,12 +848,22 @@
OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
deviceState.get(), flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
- // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
- if (!hasControlDeviceStatePermission && mRearDisplayState != null
- && state == mRearDisplayState.getIdentifier()) {
- showRearDisplayEducationalOverlayLocked(request);
+ if (mFlags.deviceStatePropertyMigration()) {
+ // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
+ if (!hasControlDeviceStatePermission && deviceState.get().hasProperty(
+ PROPERTY_FEATURE_REAR_DISPLAY)) {
+ showRearDisplayEducationalOverlayLocked(request);
+ } else {
+ mOverrideRequestController.addRequest(request);
+ }
} else {
- mOverrideRequestController.addRequest(request);
+ // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
+ if (!hasControlDeviceStatePermission && mRearDisplayState != null
+ && state == mRearDisplayState.getIdentifier()) {
+ showRearDisplayEducationalOverlayLocked(request);
+ } else {
+ mOverrideRequestController.addRequest(request);
+ }
}
}
}
@@ -1034,7 +1054,13 @@
private boolean isStateAvailableForAppRequests(int state) {
synchronized (mLock) {
- return mDeviceStatesAvailableForAppRequests.contains(state);
+ if (mFlags.deviceStatePropertyMigration()) {
+ Optional<DeviceState> deviceState = getStateLocked(state);
+ return deviceState.isPresent() && deviceState.get().hasProperty(
+ PROPERTY_POLICY_AVAILABLE_FOR_APP_REQUEST);
+ } else {
+ return mDeviceStatesAvailableForAppRequests.contains(state);
+ }
}
}
@@ -1096,9 +1122,20 @@
*/
@GuardedBy("mLock")
private boolean isDeviceOpeningLocked(int newBaseState) {
- return mBaseState.filter(
- deviceState -> mFoldedDeviceStates.contains(deviceState.getIdentifier())
- && !mFoldedDeviceStates.contains(newBaseState)).isPresent();
+ if (mFlags.deviceStatePropertyMigration()) {
+ final DeviceState currentBaseState = mBaseState.orElse(INVALID_DEVICE_STATE);
+ final DeviceState newDeviceBaseState = getStateLocked(newBaseState).orElse(
+ INVALID_DEVICE_STATE);
+
+ return currentBaseState.hasProperty(
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY)
+ && !newDeviceBaseState.hasProperty(
+ PROPERTY_FOLDABLE_DISPLAY_CONFIGURATION_OUTER_PRIMARY);
+ } else {
+ return mBaseState.filter(
+ deviceState -> mFoldedDeviceStates.contains(deviceState.getIdentifier())
+ && !mFoldedDeviceStates.contains(newBaseState)).isPresent();
+ }
}
private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index e686779..43c1f24f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -598,10 +598,11 @@
FoldSettingProvider foldSettingProvider = new FoldSettingProvider(context,
new SettingsWrapper(),
new FoldLockSettingAvailabilityProvider(context.getResources()));
+ Looper displayThreadLooper = DisplayThread.get().getLooper();
mInjector = injector;
mContext = context;
mFlags = injector.getFlags();
- mHandler = new DisplayManagerHandler(DisplayThread.get().getLooper());
+ mHandler = new DisplayManagerHandler(displayThreadLooper);
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mContext,
@@ -609,7 +610,7 @@
mDisplayDeviceRepo, new LogicalDisplayListener(), mSyncRoot, mHandler, mFlags);
mDisplayModeDirector = new DisplayModeDirector(
context, mHandler, mFlags, mDisplayDeviceConfigProvider);
- mBrightnessSynchronizer = new BrightnessSynchronizer(mContext,
+ mBrightnessSynchronizer = new BrightnessSynchronizer(mContext, displayThreadLooper,
mFlags.isBrightnessIntRangeUserPerceptionEnabled());
Resources resources = mContext.getResources();
mDefaultDisplayDefaultColorMode = mContext.getResources().getInteger(
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index de9715a..7cd9144 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -2098,7 +2098,7 @@
private void onDisplayOffloadUnblockScreenOn(DisplayOffloadSession displayOffloadSession) {
Message msg = mHandler.obtainMessage(MSG_OFFLOADING_SCREEN_ON_UNBLOCKED,
displayOffloadSession);
- mHandler.sendMessage(msg);
+ mHandler.sendMessageAtTime(msg, mClock.uptimeMillis());
}
private void unblockScreenOnByDisplayOffload() {
@@ -2116,7 +2116,7 @@
if (mDisplayOffloadSession == null) {
return;
}
- if (mPendingScreenOnUnblockerByDisplayOffload != null) {
+ if (mPendingScreenOnUnblockerByDisplayOffload == null) {
// Already unblocked.
return;
}
@@ -2134,7 +2134,6 @@
// If the screen is turning on, give displayoffload a chance to do something before the
// screen actually turns on.
- // TODO(b/316941732): add tests for this displayoffload screen-on blocker.
if (isOn && changed && !mScreenTurningOnWasBlockedByDisplayOffload) {
blockScreenOnByDisplayOffload(mDisplayOffloadSession);
} else if (!isOn && mScreenTurningOnWasBlockedByDisplayOffload) {
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/health/HealthServiceWrapper.java b/services/core/java/com/android/server/health/HealthServiceWrapper.java
index 25d1a88..9c14b5b 100644
--- a/services/core/java/com/android/server/health/HealthServiceWrapper.java
+++ b/services/core/java/com/android/server/health/HealthServiceWrapper.java
@@ -71,6 +71,21 @@
public abstract android.hardware.health.HealthInfo getHealthInfo() throws RemoteException;
/**
+ * Calls into getBatteryHealthData() in the health HAL.
+ * This function does not have a corresponding HIDL implementation, so
+ * returns null by default, unless there is an AIDL class that overrides
+ * this one.
+ *
+ * @return battery health data. {@code null} if no health HAL service.
+ * {@code null} if any service-specific error when calling {@code
+ * getBatteryHealthData}, e.g. it is unsupported.
+ * @throws RemoteException for any transaction-level errors
+ */
+ public android.hardware.health.BatteryHealthData getBatteryHealthData() throws RemoteException {
+ return null;
+ }
+
+ /**
* Create a new HealthServiceWrapper instance.
*
* @param healthInfoCallback the callback to call when health info changes
diff --git a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
index fd3a92e..2a3fbc3 100644
--- a/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
+++ b/services/core/java/com/android/server/health/HealthServiceWrapperAidl.java
@@ -212,6 +212,17 @@
}
}
+ @Override
+ public BatteryHealthData getBatteryHealthData() throws RemoteException {
+ IHealth service = mLastService.get();
+ if (service == null) return null;
+ try {
+ return service.getBatteryHealthData();
+ } catch (UnsupportedOperationException | ServiceSpecificException ex) {
+ return null;
+ }
+ }
+
public void setChargingPolicy(int policy) throws RemoteException {
IHealth service = mLastService.get();
if (service == null) return;
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 38a0d37..62c21bd 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -83,6 +83,7 @@
private final Handler mHandler;
private final UEventManager mUEventManager;
private final BluetoothBatteryManager mBluetoothBatteryManager;
+ private final Runnable mHandlePollEventCallback = this::handlePollEvent;
// Maps a pid to the registered listener record for that process. There can only be one battery
// listener per process.
@@ -206,7 +207,7 @@
if (!mIsInteractive || !anyOf(mDeviceMonitors, DeviceMonitor::requiresPolling)) {
// Stop polling.
mIsPolling = false;
- mHandler.removeCallbacks(this::handlePollEvent);
+ mHandler.removeCallbacks(mHandlePollEventCallback);
return;
}
@@ -215,7 +216,7 @@
}
// Start polling.
mIsPolling = true;
- mHandler.postDelayed(this::handlePollEvent, delayStart ? POLLING_PERIOD_MILLIS : 0);
+ mHandler.postDelayed(mHandlePollEventCallback, delayStart ? POLLING_PERIOD_MILLIS : 0);
}
private <R> R processInputDevice(int deviceId, R defaultValue, Function<InputDevice, R> func) {
@@ -366,7 +367,7 @@
}
final long eventTime = SystemClock.uptimeMillis();
mDeviceMonitors.forEach((deviceId, monitor) -> monitor.onPoll(eventTime));
- mHandler.postDelayed(this::handlePollEvent, POLLING_PERIOD_MILLIS);
+ mHandler.postDelayed(mHandlePollEventCallback, POLLING_PERIOD_MILLIS);
}
}
diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
index b77f47d..aa4b338 100644
--- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
+++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java
@@ -81,6 +81,7 @@
@InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
@SoftInputShowHideReason int reason, @UserIdInt int userId) {
final var bindingController = mService.getInputMethodBindingController(userId);
+ final var userData = mService.getUserData(userId);
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null) {
if (DEBUG) {
@@ -93,10 +94,10 @@
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_SHOW_IME,
statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
- Objects.toString(mService.mImeBindingState.mFocusedWindow),
+ Objects.toString(userData.mImeBindingState.mFocusedWindow),
InputMethodDebug.softInputDisplayReasonToString(reason),
InputMethodDebug.softInputModeToString(
- mService.mImeBindingState.mFocusedWindowSoftInputMode));
+ userData.mImeBindingState.mFocusedWindowSoftInputMode));
}
mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
statsToken, userId);
@@ -111,6 +112,7 @@
@UserIdInt int userId) {
final var bindingController = mService.getInputMethodBindingController(userId);
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
+ final var userData = mService.getUserData(userId);
if (curMethod != null) {
// The IME will report its visible state again after the following message finally
// delivered to the IME process as an IPC. Hence the inconsistency between
@@ -126,10 +128,10 @@
if (DEBUG_IME_VISIBILITY) {
EventLog.writeEvent(IMF_HIDE_IME,
statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
- Objects.toString(mService.mImeBindingState.mFocusedWindow),
+ Objects.toString(userData.mImeBindingState.mFocusedWindow),
InputMethodDebug.softInputDisplayReasonToString(reason),
InputMethodDebug.softInputModeToString(
- mService.mImeBindingState.mFocusedWindowSoftInputMode));
+ userData.mImeBindingState.mFocusedWindowSoftInputMode));
}
mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
statsToken, userId);
@@ -179,29 +181,30 @@
break;
case STATE_HIDE_IME_EXPLICIT:
if (Flags.refactorInsetsController()) {
- setImeVisibilityOnFocusedWindowClient(false);
+ setImeVisibilityOnFocusedWindowClient(false, userId);
} else {
mService.hideCurrentInputLocked(windowToken, statsToken,
- 0 /* flags */, null /* resultReceiver */, reason);
+ 0 /* flags */, null /* resultReceiver */, reason, userId);
}
break;
case STATE_HIDE_IME_NOT_ALWAYS:
if (Flags.refactorInsetsController()) {
- setImeVisibilityOnFocusedWindowClient(false);
+ setImeVisibilityOnFocusedWindowClient(false, userId);
} else {
mService.hideCurrentInputLocked(windowToken, statsToken,
- InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */, reason);
+ InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */, reason,
+ userId);
}
break;
case STATE_SHOW_IME_IMPLICIT:
if (Flags.refactorInsetsController()) {
// This can be triggered by IMMS#startInputOrWindowGainedFocus. We need to
// set the requestedVisibleTypes in InsetsController first, before applying it.
- setImeVisibilityOnFocusedWindowClient(true);
+ setImeVisibilityOnFocusedWindowClient(true, userId);
} else {
mService.showCurrentInputLocked(windowToken, statsToken,
InputMethodManager.SHOW_IMPLICIT, MotionEvent.TOOL_TYPE_UNKNOWN,
- null /* resultReceiver */, reason);
+ null /* resultReceiver */, reason, userId);
}
break;
case STATE_SHOW_IME_SNAPSHOT:
@@ -230,20 +233,23 @@
@GuardedBy("ImfLock.class")
@Override
public boolean removeImeScreenshot(int displayId, @UserIdInt int userId) {
+ final var userData = mService.getUserData(userId);
if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) {
mService.onShowHideSoftInputRequested(false /* show */,
- mService.mImeBindingState.mFocusedWindow,
+ userData.mImeBindingState.mFocusedWindow,
REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */, userId);
return true;
}
return false;
}
- private void setImeVisibilityOnFocusedWindowClient(boolean visibility) {
- if (mService.mImeBindingState != null
- && mService.mImeBindingState.mFocusedWindowClient != null
- && mService.mImeBindingState.mFocusedWindowClient.mClient != null) {
- mService.mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(visibility);
+ @GuardedBy("ImfLock.class")
+ private void setImeVisibilityOnFocusedWindowClient(boolean visibility, @UserIdInt int userId) {
+ final var userData = mService.getUserData(userId);
+ if (userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindowClient != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
+ userData.mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(visibility);
} else {
// TODO(b/329229469): ImeTracker?
}
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 9d80844..7ebf595 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -38,6 +38,7 @@
import android.accessibilityservice.AccessibilityService;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.UserIdInt;
import android.content.res.Configuration;
import android.os.Binder;
import android.os.IBinder;
@@ -52,6 +53,7 @@
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodManager;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.server.LocalServices;
@@ -553,15 +555,17 @@
return null;
}
- IBinder getWindowTokenFrom(IBinder requestImeToken) {
+ @GuardedBy("ImfLock.class")
+ IBinder getWindowTokenFrom(IBinder requestImeToken, @UserIdInt int userId) {
for (IBinder windowToken : mRequestWindowStateMap.keySet()) {
final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken);
if (state.getRequestImeToken() == requestImeToken) {
return windowToken;
}
}
+ final var userData = mService.getUserData(userId);
// Fallback to the focused window for some edge cases (e.g. relaunching the activity)
- return mService.mImeBindingState.mFocusedWindow;
+ return userData.mImeBindingState.mFocusedWindow;
}
IBinder getWindowTokenFrom(ImeTargetWindowState windowState) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 60d647d..afc1029 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -382,9 +382,9 @@
InputMethodManager
.invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
}
- mService.initializeImeLocked(mCurMethod, mCurToken);
+ mService.initializeImeLocked(mCurMethod, mCurToken, mUserId);
mService.scheduleNotifyImeUidToAudioService(mCurMethodUid);
- mService.reRequestCurrentClientSessionLocked();
+ mService.reRequestCurrentClientSessionLocked(mUserId);
mAutofillController.performOnCreateInlineSuggestionsRequest();
}
@@ -437,7 +437,7 @@
mLastBindTime = SystemClock.uptimeMillis();
clearCurMethodAndSessions();
mService.clearInputShownLocked();
- mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME);
+ mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME, mUserId);
}
}
}
@@ -483,7 +483,7 @@
@GuardedBy("ImfLock.class")
private void clearCurMethodAndSessions() {
- mService.clearClientSessionsLocked();
+ mService.clearClientSessionsLocked(this);
mCurMethod = null;
mCurMethodUid = Process.INVALID_UID;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index a089331..3c74b23 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -468,12 +468,6 @@
private final ClientController mClientController;
/**
- * Holds the current IME binding state info.
- */
- @MultiUserUnawareField
- ImeBindingState mImeBindingState;
-
- /**
* Set once the system is ready to run third party code.
*/
@SharedByAllUsersField
@@ -492,25 +486,6 @@
}
- /**
- * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
- * This is to be synchronized with the secure settings keyed with
- * {@link Settings.Secure#DEFAULT_INPUT_METHOD}.
- *
- * <p>This can be transiently {@code null} when the system is re-initializing input method
- * settings, e.g., the system locale is just changed.</p>
- *
- * <p>Note that {@link InputMethodBindingController#getCurId()} is used to track which IME
- * is being connected to {@link InputMethodManagerService}.</p>
- *
- * @see InputMethodBindingController#getCurId()
- */
- @GuardedBy("ImfLock.class")
- @Nullable
- String getSelectedMethodIdLocked() {
- return getInputMethodBindingController(mCurrentUserId).getSelectedMethodId();
- }
-
@GuardedBy("ImfLock.class")
@Nullable
InputMethodInfo queryInputMethodForCurrentUserLocked(@NonNull String imeId) {
@@ -518,13 +493,6 @@
}
/**
- * The client that is currently bound to an input method.
- */
- @MultiUserUnawareField
- @Nullable
- private ClientState mCurClient;
-
- /**
* The last window token that we confirmed that IME started talking to. This is always updated
* upon reports from the input method. If the window state is already changed before the report
* is handled, this field just keeps the last value.
@@ -533,33 +501,6 @@
IBinder mLastImeTargetWindow;
/**
- * The {@link IRemoteInputConnection} last provided by the current client.
- */
- @MultiUserUnawareField
- IRemoteInputConnection mCurInputConnection;
-
- /**
- * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
- * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
- */
- @MultiUserUnawareField
- ImeOnBackInvokedDispatcher mCurImeDispatcher;
-
- /**
- * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
- */
- @MultiUserUnawareField
- @Nullable
- IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
-
- /**
- * The {@link EditorInfo} last provided by the current client.
- */
- @MultiUserUnawareField
- @Nullable
- EditorInfo mCurEditorInfo;
-
- /**
* Map of window perceptible states indexed by their associated window tokens.
*
* The value {@code true} indicates that IME has not been mostly hidden via
@@ -570,20 +511,6 @@
private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>();
/**
- * The token tracking the current IME show request that is waiting for a connection to an IME,
- * otherwise {@code null}.
- */
- @Nullable
- @MultiUserUnawareField
- private ImeTracker.Token mCurStatsToken;
-
- /**
- * {@code true} if the current input method is in fullscreen mode.
- */
- @MultiUserUnawareField
- boolean mInFullscreenMode;
-
- /**
* The token we have made for the currently active input method, to
* identify it in the future.
*/
@@ -618,27 +545,6 @@
}
/**
- * Have we called mCurMethod.bindInput()?
- */
- @MultiUserUnawareField
- boolean mBoundToMethod;
-
- /**
- * Have we called bindInput() for accessibility services?
- */
- @MultiUserUnawareField
- boolean mBoundToAccessibility;
-
- /**
- * Currently enabled session.
- */
- @GuardedBy("ImfLock.class")
- @MultiUserUnawareField
- SessionState mEnabledSession;
- @MultiUserUnawareField
- SparseArray<AccessibilitySessionState> mEnabledAccessibilitySessions = new SparseArray<>();
-
- /**
* True if the device is currently interactive with user. The value is true initially.
*/
@MultiUserUnawareField
@@ -763,13 +669,15 @@
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, mUserId);
mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard(
accessibilitySoftKeyboardSetting);
+ final var userData = getUserData(mUserId);
if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) {
- hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
- SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE);
- } else if (isShowRequestedForCurrentWindow()) {
- showCurrentInputLocked(mImeBindingState.mFocusedWindow,
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
+ 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE,
+ mUserId);
+ } else if (isShowRequestedForCurrentWindow(mUserId)) {
+ showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
InputMethodManager.SHOW_IMPLICIT,
- SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE);
+ SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, mUserId);
}
} else if (stylusHandwritingEnabledUri.equals(uri)) {
InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches();
@@ -777,13 +685,13 @@
.invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches();
} else {
boolean enabledChanged = false;
- String newEnabled = InputMethodSettingsRepository.get(mCurrentUserId)
+ String newEnabled = InputMethodSettingsRepository.get(mUserId)
.getEnabledInputMethodsStr();
if (!mLastEnabled.equals(newEnabled)) {
mLastEnabled = newEnabled;
enabledChanged = true;
}
- updateInputMethodsFromSettingsLocked(enabledChanged);
+ updateInputMethodsFromSettingsLocked(enabledChanged, mUserId);
}
}
}
@@ -846,10 +754,12 @@
DirectBootAwareness.AUTO);
InputMethodSettingsRepository.put(userId, settings);
}
- postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */);
+ // TODO(b/305849394): Dispatch this to non-current users.
+ final int userId = mCurrentUserId;
+ postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId);
// If the locale is changed, needs to reset the default ime
- resetDefaultImeLocked(mContext);
- updateFromSettingsLocked(true);
+ resetDefaultImeLocked(mContext, userId);
+ updateFromSettingsLocked(true, userId);
}
}
@@ -1004,7 +914,7 @@
if (!isCurrentUser) {
return;
}
- postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
boolean changed = false;
@@ -1046,7 +956,7 @@
}
if (changed) {
- updateFromSettingsLocked(false);
+ updateFromSettingsLocked(false, userId);
}
}
}
@@ -1188,8 +1098,8 @@
InputMethodSettingsRepository.put(userId, newSettings);
if (mCurrentUserId == userId) {
// We need to rebuild IMEs.
- postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
- updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId);
+ updateInputMethodsFromSettingsLocked(true /* enabledChanged */, userId);
} else if (mExperimentalConcurrentMultiUserModeEnabled) {
experimentalInitializeVisibleBackgroundUserLocked(userId);
}
@@ -1208,8 +1118,9 @@
}
// Hide soft input before user switch task since switch task may block main handler a while
// and delayed the hideCurrentInputLocked().
- hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
- SoftInputShowHideReason.HIDE_SWITCH_USER);
+ final var userData = getUserData(userId);
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
+ SoftInputShowHideReason.HIDE_SWITCH_USER, userId);
final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId,
clientToBeReset);
mUserSwitchHandlerTask = task;
@@ -1301,7 +1212,6 @@
mClientController = new ClientController(mPackageManagerInternal);
mClientController.addClientControllerCallback(c -> onClientRemoved(c));
- mImeBindingState = ImeBindingState.newEmptyState();
mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
@@ -1350,10 +1260,11 @@
}
@GuardedBy("ImfLock.class")
- private void resetDefaultImeLocked(Context context) {
+ private void resetDefaultImeLocked(Context context, @UserIdInt int userId) {
+ final var bindingController = getInputMethodBindingController(userId);
// Do not reset the default (current) IME when it is a 3rd-party IME
- String selectedMethodId = getSelectedMethodIdLocked();
- final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ String selectedMethodId = bindingController.getSelectedMethodId();
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (selectedMethodId != null
&& !settings.getMethodMap().get(selectedMethodId).isSystem()) {
return;
@@ -1368,7 +1279,7 @@
if (DEBUG) {
Slog.i(TAG, "Default found, using " + defIm.getId());
}
- setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
+ setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false, userId);
}
@GuardedBy("ImfLock.class")
@@ -1413,22 +1324,23 @@
@GuardedBy("ImfLock.class")
private void switchUserOnHandlerLocked(@UserIdInt int newUserId,
IInputMethodClientInvoker clientToBeReset) {
+ final int prevUserId = mCurrentUserId;
if (DEBUG) {
Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
- + " currentUserId=" + mCurrentUserId);
+ + " prevUserId=" + prevUserId);
}
// Clean up stuff for mCurrentUserId, which soon becomes the previous user.
// TODO(b/338461930): Check if this is still necessary or not.
- onUnbindCurrentMethodByReset();
+ onUnbindCurrentMethodByReset(prevUserId);
// Note that in b/197848765 we want to see if we can keep the binding alive for better
// profile switching.
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ final var bindingController = getInputMethodBindingController(prevUserId);
bindingController.unbindCurrentMethod();
- unbindCurrentClientLocked(UnbindReason.SWITCH_USER);
+ unbindCurrentClientLocked(UnbindReason.SWITCH_USER, prevUserId);
// Hereafter we start initializing things for "newUserId".
@@ -1438,6 +1350,7 @@
mSettingsObserver.registerContentObserverLocked(newUserId);
mCurrentUserId = newUserId;
+ final var newUserData = getUserData(newUserId);
final String defaultImiId = SecureSettingsWrapper.getString(
Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId);
@@ -1454,13 +1367,14 @@
final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId);
- postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */);
+ postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */,
+ newUserId);
if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) {
// This is the first time of the user switch and
// set the current ime to the proper one.
- resetDefaultImeLocked(mContext);
+ resetDefaultImeLocked(mContext, newUserId);
}
- updateFromSettingsLocked(true);
+ updateFromSettingsLocked(true, newUserId);
if (initialUserSwitch) {
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
@@ -1479,7 +1393,7 @@
// The client is already gone.
return;
}
- cs.mClient.scheduleStartInputIfNecessary(mInFullscreenMode);
+ cs.mClient.scheduleStartInputIfNecessary(newUserData.mInFullscreenMode);
}
}
@@ -1541,8 +1455,8 @@
DirectBootAwareness.AUTO);
InputMethodSettingsRepository.put(currentUserId, newSettings);
postInputMethodSettingUpdatedLocked(
- !imeSelectedOnBoot /* resetDefaultEnabledIme */);
- updateFromSettingsLocked(true);
+ !imeSelectedOnBoot /* resetDefaultEnabledIme */, currentUserId);
+ updateFromSettingsLocked(true, currentUserId);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, currentUserId),
newSettings.getEnabledInputMethodList());
@@ -1859,20 +1773,31 @@
}
}
+ @GuardedBy("ImfLock.class")
+ private void onClientRemoved(ClientState client) {
+ clearClientSessionLocked(client);
+ clearClientSessionForAccessibilityLocked(client);
+ // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
+ @SuppressWarnings("GuardedBy") Consumer<UserDataRepository.UserData> clientRemovedForUser =
+ userData -> onClientRemovedInternalLocked(client, userData);
+ mUserDataRepository.forAllUserData(clientRemovedForUser);
+ }
+
/**
* Hide the IME if the removed user is the current user.
*/
// TODO(b/325515685): Move this method to InputMethodBindingController
@GuardedBy("ImfLock.class")
- private void onClientRemoved(ClientState client) {
- clearClientSessionLocked(client);
- clearClientSessionForAccessibilityLocked(client);
- if (mCurClient == client) {
- hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
- SoftInputShowHideReason.HIDE_REMOVE_CLIENT);
- if (mBoundToMethod) {
- mBoundToMethod = false;
- IInputMethodInvoker curMethod = getCurMethodLocked();
+ private void onClientRemovedInternalLocked(ClientState client,
+ @NonNull UserDataRepository.UserData userData) {
+ final int userId = userData.mUserId;
+ if (userData.mCurClient == client) {
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
+ SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId);
+ if (userData.mBoundToMethod) {
+ userData.mBoundToMethod = false;
+ final var userBindingController = getInputMethodBindingController(userId);
+ IInputMethodInvoker curMethod = userBindingController.getCurMethod();
if (curMethod != null) {
// When we unbind input, we are unbinding the client, so we always
// unbind ime and a11y together.
@@ -1880,10 +1805,10 @@
AccessibilityManagerInternal.get().unbindInput();
}
}
- mBoundToAccessibility = false;
- mCurClient = null;
- if (mImeBindingState.mFocusedWindowClient == client) {
- mImeBindingState = ImeBindingState.newEmptyState();
+ userData.mBoundToAccessibility = false;
+ userData.mCurClient = null;
+ if (userData.mImeBindingState.mFocusedWindowClient == client) {
+ userData.mImeBindingState = ImeBindingState.newEmptyState();
}
}
}
@@ -1896,49 +1821,51 @@
}
@GuardedBy("ImfLock.class")
- void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
- if (mCurClient != null) {
+ void unbindCurrentClientLocked(@UnbindReason int unbindClientReason, @UserIdInt int userId) {
+ final var userData = getUserData(userId);
+ if (userData.mCurClient != null) {
if (DEBUG) {
- Slog.v(TAG, "unbindCurrentInputLocked: client=" + mCurClient.mClient.asBinder());
+ Slog.v(TAG, "unbindCurrentInputLocked: client="
+ + userData.mCurClient.mClient.asBinder());
}
- if (mBoundToMethod) {
- mBoundToMethod = false;
- IInputMethodInvoker curMethod = getCurMethodLocked();
+ final var bindingController = getInputMethodBindingController(userId);
+ if (userData.mBoundToMethod) {
+ userData.mBoundToMethod = false;
+ IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null) {
curMethod.unbindInput();
}
}
- mBoundToAccessibility = false;
+ userData.mBoundToAccessibility = false;
// Since we set active false to current client and set mCurClient to null, let's unbind
// all accessibility too. That means, when input method get disconnected (including
// switching ime), we also unbind accessibility
- mCurClient.mClient.setActive(false /* active */, false /* fullscreen */);
+ userData.mCurClient.mClient.setActive(false /* active */, false /* fullscreen */);
- // TODO(b/325515685): make binding controller user independent. Before this change, the
- // following dependencies also need to be user independent: mCurClient, mBoundToMethod,
- // getCurMethodLocked(), and mMenuController.
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
- mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(),
+ userData.mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(),
unbindClientReason);
- mCurClient.mSessionRequested = false;
- mCurClient.mSessionRequestedForAccessibility = false;
- mCurClient = null;
- ImeTracker.forLogging().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
- mCurStatsToken = null;
+ userData.mCurClient.mSessionRequested = false;
+ userData.mCurClient.mSessionRequestedForAccessibility = false;
+ userData.mCurClient = null;
+ ImeTracker.forLogging().onFailed(userData.mCurStatsToken,
+ ImeTracker.PHASE_SERVER_WAIT_IME);
+ userData.mCurStatsToken = null;
+ // TODO: Make mMenuController multi-user aware
mMenuController.hideInputMethodMenuLocked();
}
}
/**
* TODO(b/338404383) Remove
- * Called when {@link #resetCurrentMethodAndClientLocked(int)} invoked for clean-up states
+ * Called when {@link #resetCurrentMethodAndClientLocked(int, int)} invoked for clean-up states
* before unbinding the current method.
*/
@GuardedBy("ImfLock.class")
- void onUnbindCurrentMethodByReset() {
+ void onUnbindCurrentMethodByReset(@UserIdInt int userId) {
+ final var userData = getUserData(userId);
final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
- mImeBindingState.mFocusedWindow);
+ userData.mImeBindingState.mFocusedWindow);
if (winState != null && !winState.isRequestedImeVisible()
&& !mVisibilityStateComputer.isInputShown()) {
// Normally, the focus window will apply the IME visibility state to
@@ -1949,9 +1876,9 @@
// As a result, we have to notify WM to apply IME visibility before clearing the
// binding states in the first place.
final var statsToken = createStatsTokenForFocusedClient(false /* show */,
- SoftInputShowHideReason.UNBIND_CURRENT_METHOD);
- mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken,
- STATE_HIDE_IME, mCurrentUserId);
+ SoftInputShowHideReason.UNBIND_CURRENT_METHOD, userId);
+ mVisibilityApplier.applyImeVisibility(userData.mImeBindingState.mFocusedWindow,
+ statsToken, STATE_HIDE_IME, userId);
}
}
@@ -1961,13 +1888,13 @@
*/
@GuardedBy("ImfLock.class")
boolean hasAttachedClient() {
- return mCurClient != null;
+ return getUserData(mCurrentUserId).mCurClient != null;
}
@VisibleForTesting
void setAttachedClientForTesting(@NonNull ClientState cs) {
synchronized (ImfLock.class) {
- mCurClient = cs;
+ getUserData(mCurrentUserId).mCurClient = cs;
}
}
@@ -1983,20 +1910,23 @@
}
@GuardedBy("ImfLock.class")
- private boolean isShowRequestedForCurrentWindow() {
+ private boolean isShowRequestedForCurrentWindow(@UserIdInt int userId) {
+ final var userData = getUserData(userId);
+ // TODO(b/349904272): Make mVisibilityStateComputer multi-user aware
final ImeTargetWindowState state = mVisibilityStateComputer.getWindowStateOrNull(
- mImeBindingState.mFocusedWindow);
+ userData.mImeBindingState.mFocusedWindow);
return state != null && state.isRequestedImeVisible();
}
@GuardedBy("ImfLock.class")
@NonNull
- InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) {
- final int userId = mCurrentUserId;
+ InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial,
+ @UserIdInt int userId) {
final var bindingController = getInputMethodBindingController(userId);
- if (!mBoundToMethod) {
- bindingController.getCurMethod().bindInput(mCurClient.mBinding);
- mBoundToMethod = true;
+ final var userData = getUserData(userId);
+ if (!userData.mBoundToMethod) {
+ bindingController.getCurMethod().bindInput(userData.mCurClient.mBinding);
+ userData.mBoundToMethod = true;
}
final boolean restarting = !initial;
@@ -2004,11 +1934,12 @@
final StartInputInfo info = new StartInputInfo(mCurrentUserId,
bindingController.getCurToken(), bindingController.getCurTokenDisplayId(),
bindingController.getCurId(), startInputReason,
- restarting, UserHandle.getUserId(mCurClient.mUid),
- mCurClient.mSelfReportedDisplayId, mImeBindingState.mFocusedWindow, mCurEditorInfo,
- mImeBindingState.mFocusedWindowSoftInputMode,
+ restarting, UserHandle.getUserId(userData.mCurClient.mUid),
+ userData.mCurClient.mSelfReportedDisplayId,
+ userData.mImeBindingState.mFocusedWindow, userData.mCurEditorInfo,
+ userData.mImeBindingState.mFocusedWindowSoftInputMode,
bindingController.getSequenceNumber());
- mImeTargetWindowMap.put(startInputToken, mImeBindingState.mFocusedWindow);
+ mImeTargetWindowMap.put(startInputToken, userData.mImeBindingState.mFocusedWindow);
mStartInputHistory.addEntry(info);
// Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user
@@ -2016,33 +1947,34 @@
// same-user scenarios.
// That said ignoring cross-user scenario will never affect IMEs that do not have
// INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case.
- if (userId == UserHandle.getUserId(mCurClient.mUid)) {
+ if (userId == UserHandle.getUserId(userData.mCurClient.mUid)) {
mPackageManagerInternal.grantImplicitAccess(userId, null /* intent */,
UserHandle.getAppId(bindingController.getCurMethodUid()),
- mCurClient.mUid, true /* direct */);
+ userData.mCurClient.mUid, true /* direct */);
}
@InputMethodNavButtonFlags final int navButtonFlags = getInputMethodNavButtonFlagsLocked();
- final SessionState session = mCurClient.mCurSession;
- setEnabledSessionLocked(session);
- session.mMethod.startInput(startInputToken, mCurInputConnection, mCurEditorInfo, restarting,
- navButtonFlags, mCurImeDispatcher);
+ final SessionState session = userData.mCurClient.mCurSession;
+ setEnabledSessionLocked(session, userData);
+ session.mMethod.startInput(startInputToken, userData.mCurInputConnection,
+ userData.mCurEditorInfo, restarting, navButtonFlags, userData.mCurImeDispatcher);
if (Flags.refactorInsetsController()) {
- if (isShowRequestedForCurrentWindow() && mImeBindingState != null
- && mImeBindingState.mFocusedWindow != null) {
- showSoftInputInternal(mImeBindingState.mFocusedWindow);
+ if (isShowRequestedForCurrentWindow(userId) && userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindow != null) {
+ showSoftInputInternal(userData.mImeBindingState.mFocusedWindow);
}
} else {
- if (isShowRequestedForCurrentWindow()) {
+ if (isShowRequestedForCurrentWindow(userId)) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
// Re-use current statsToken, if it exists.
- final var statsToken = mCurStatsToken != null ? mCurStatsToken
+ final var statsToken = userData.mCurStatsToken != null ? userData.mCurStatsToken
: createStatsTokenForFocusedClient(true /* show */,
- SoftInputShowHideReason.ATTACH_NEW_INPUT);
- mCurStatsToken = null;
- showCurrentInputLocked(mImeBindingState.mFocusedWindow, statsToken,
+ SoftInputShowHideReason.ATTACH_NEW_INPUT, userId);
+ userData.mCurStatsToken = null;
+ showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, statsToken,
mVisibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN,
- null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT);
+ null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT,
+ userId);
}
}
@@ -2052,7 +1984,8 @@
final boolean suppressesSpellChecker =
curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker();
final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions =
- createAccessibilityInputMethodSessions(mCurClient.mAccessibilitySessions);
+ createAccessibilityInputMethodSessions(
+ userData.mCurClient.mAccessibilitySessions);
if (bindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) {
mHwController.setInkWindowInitializer(new InkWindowInitializer());
}
@@ -2064,10 +1997,12 @@
@GuardedBy("ImfLock.class")
private void attachNewAccessibilityLocked(@StartInputReason int startInputReason,
- boolean initial) {
- if (!mBoundToAccessibility) {
+ boolean initial, @UserIdInt int userId) {
+ final var userData = getUserData(userId);
+
+ if (!userData.mBoundToAccessibility) {
AccessibilityManagerInternal.get().bindInput();
- mBoundToAccessibility = true;
+ userData.mBoundToAccessibility = true;
}
// TODO(b/187453053): grantImplicitAccess to accessibility services access? if so, need to
@@ -2076,9 +2011,11 @@
// We don't start input when session for a11y is created. We start input when
// input method start input, a11y manager service is always on.
if (startInputReason != StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY) {
- setEnabledSessionForAccessibilityLocked(mCurClient.mAccessibilitySessions);
- AccessibilityManagerInternal.get().startInput(mCurRemoteAccessibilityInputConnection,
- mCurEditorInfo, !initial /* restarting */);
+ setEnabledSessionForAccessibilityLocked(userData.mCurClient.mAccessibilitySessions,
+ userData);
+ AccessibilityManagerInternal.get().startInput(
+ userData.mCurRemoteAccessibilityInputConnection,
+ userData.mCurEditorInfo, !initial /* restarting */);
}
}
@@ -2114,10 +2051,13 @@
@NonNull ImeOnBackInvokedDispatcher imeDispatcher,
@NonNull InputMethodBindingController bindingController) {
+ final int userId = bindingController.mUserId;
+ final var userData = getUserData(userId);
+
// Compute the final shown display ID with validated cs.selfReportedDisplayId for this
// session & other conditions.
ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull(
- mImeBindingState.mFocusedWindow);
+ userData.mImeBindingState.mFocusedWindow);
if (winState == null) {
return InputBindResult.NOT_IME_TARGET_WINDOW;
}
@@ -2134,13 +2074,13 @@
mVisibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true);
} else if (!Objects.equals(deviceMethodId, selectedMethodId)) {
setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_ID,
- bindingController.getDeviceIdToShowIme());
+ bindingController.getDeviceIdToShowIme(), userId);
selectedMethodId = deviceMethodId;
}
if (mVisibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) {
- hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
- SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE);
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
+ SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE, userId);
return InputBindResult.NO_IME;
}
@@ -2149,19 +2089,19 @@
return InputBindResult.NO_IME;
}
- if (mCurClient != cs) {
- prepareClientSwitchLocked(cs);
+ if (userData.mCurClient != cs) {
+ prepareClientSwitchLocked(cs, userId);
}
- final boolean connectionWasActive = mCurInputConnection != null;
+ final boolean connectionWasActive = userData.mCurInputConnection != null;
// Bump up the sequence for this client and attach it.
bindingController.advanceSequenceNumber();
- mCurClient = cs;
- mCurInputConnection = inputConnection;
- mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection;
- mCurImeDispatcher = imeDispatcher;
+ userData.mCurClient = cs;
+ userData.mCurInputConnection = inputConnection;
+ userData.mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection;
+ userData.mCurImeDispatcher = imeDispatcher;
// Override the locale hints if the app is running on a virtual device.
if (mVdmInternal == null) {
mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class);
@@ -2172,17 +2112,17 @@
editorInfo.hintLocales = hintsFromVirtualDevice;
}
}
- mCurEditorInfo = editorInfo;
+ userData.mCurEditorInfo = editorInfo;
// Notify input manager if the connection state changes.
- final boolean connectionIsActive = mCurInputConnection != null;
+ final boolean connectionIsActive = userData.mCurInputConnection != null;
if (connectionIsActive != connectionWasActive) {
mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive);
}
// If configured, we want to avoid starting up the IME if it is not supposed to be showing
if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags,
- unverifiedTargetSdkVersion)) {
+ unverifiedTargetSdkVersion, userId)) {
if (DEBUG) {
Slog.d(TAG, "Avoiding IME startup and unbinding current input method.");
}
@@ -2197,7 +2137,7 @@
final String curId = bindingController.getCurId();
final int displayIdToShowIme = bindingController.getDisplayIdToShowIme();
if (curId != null && curId.equals(bindingController.getSelectedMethodId())
- && displayIdToShowIme == getCurTokenDisplayIdLocked()) {
+ && displayIdToShowIme == bindingController.getCurTokenDisplayId()) {
if (cs.mCurSession != null) {
// Fast case: if we are already connected to the input method,
// then just return it.
@@ -2211,12 +2151,12 @@
// we can always attach to accessibility because AccessibilityManagerService is
// always on.
attachNewAccessibilityLocked(startInputReason,
- (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
+ (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0, userId);
return attachNewInputLocked(startInputReason,
- (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0);
+ (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0, userId);
}
- InputBindResult bindResult = tryReuseConnectionLocked(bindingController, cs);
+ InputBindResult bindResult = tryReuseConnectionLocked(bindingController, cs, userId);
if (bindResult != null) {
return bindResult;
}
@@ -2293,18 +2233,19 @@
private boolean shouldPreventImeStartupLocked(
@NonNull String selectedMethodId,
@StartInputFlags int startInputFlags,
- int unverifiedTargetSdkVersion) {
+ int unverifiedTargetSdkVersion,
+ @UserIdInt int userId) {
// Fast-path for the majority of cases
if (!mPreventImeStartupUnlessTextEditor) {
return false;
}
- if (isShowRequestedForCurrentWindow()) {
+ if (isShowRequestedForCurrentWindow(userId)) {
return false;
}
if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) {
return false;
}
- final InputMethodInfo imi = InputMethodSettingsRepository.get(mCurrentUserId)
+ final InputMethodInfo imi = InputMethodSettingsRepository.get(userId)
.getMethodMap().get(selectedMethodId);
if (imi == null) {
return false;
@@ -2316,10 +2257,10 @@
}
@GuardedBy("ImfLock.class")
- private void prepareClientSwitchLocked(ClientState cs) {
+ private void prepareClientSwitchLocked(ClientState cs, @UserIdInt int userId) {
// If the client is changing, we need to switch over to the new
// one.
- unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT);
+ unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT, userId);
// If the screen is on, inform the new client it is active
if (mIsInteractive) {
cs.mClient.setActive(true /* active */, false /* fullscreen */);
@@ -2329,13 +2270,14 @@
@GuardedBy("ImfLock.class")
@Nullable
private InputBindResult tryReuseConnectionLocked(
- @NonNull InputMethodBindingController bindingController, @NonNull ClientState cs) {
+ @NonNull InputMethodBindingController bindingController, @NonNull ClientState cs,
+ @UserIdInt int userId) {
if (bindingController.hasMainConnection()) {
- if (getCurMethodLocked() != null) {
+ if (bindingController.getCurMethod() != null) {
if (!Flags.useZeroJankProxy()) {
// Return to client, and we will get back with it when
// we have had a session made for it.
- requestClientSessionLocked(cs);
+ requestClientSessionLocked(cs, userId);
requestClientSessionForAccessibilityLocked(cs);
}
return new InputBindResult(
@@ -2361,7 +2303,7 @@
bindingController.getSequenceNumber(), false);
} else {
EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
- getSelectedMethodIdLocked(), bindingDuration, 0);
+ bindingController.getSelectedMethodId(), bindingDuration, 0);
}
}
}
@@ -2402,12 +2344,15 @@
}
@GuardedBy("ImfLock.class")
- void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token) {
+ void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token,
+ @UserIdInt int userId) {
if (DEBUG) {
Slog.v(TAG, "Sending attach of token: " + token + " for display: "
- + getCurTokenDisplayIdLocked());
+ + getInputMethodBindingController(userId).getCurTokenDisplayId());
}
- inputMethod.initializeInternal(token, new InputMethodPrivilegedOperationsImpl(this, token),
+ inputMethod.initializeInternal(token,
+ new InputMethodPrivilegedOperationsImpl(this, token, userId),
+ // TODO(b/345519864): Make getInputMethodNavButtonFlagsLocked() multi-user aware
getInputMethodNavButtonFlagsLocked());
}
@@ -2438,7 +2383,7 @@
@BinderThread
void onSessionCreated(IInputMethodInvoker method, IInputMethodSession session,
- InputChannel channel) {
+ InputChannel channel, @UserIdInt int userId) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated");
try {
synchronized (ImfLock.class) {
@@ -2448,18 +2393,21 @@
channel.dispose();
return;
}
- IInputMethodInvoker curMethod = getCurMethodLocked();
+ final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
+ IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null && method != null
&& curMethod.asBinder() == method.asBinder()) {
- if (mCurClient != null) {
- clearClientSessionLocked(mCurClient);
- mCurClient.mCurSession = new SessionState(
- mCurClient, method, session, channel);
+ if (userData.mCurClient != null) {
+ clearClientSessionLocked(userData.mCurClient);
+ userData.mCurClient.mCurSession = new SessionState(
+ userData.mCurClient, method, session, channel);
InputBindResult res = attachNewInputLocked(
- StartInputReason.SESSION_CREATED_BY_IME, true);
- attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_IME, true);
+ StartInputReason.SESSION_CREATED_BY_IME, true, userId);
+ attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_IME, true,
+ userId);
if (res.method != null) {
- mCurClient.mClient.onBindMethod(res);
+ userData.mCurClient.mClient.onBindMethod(res);
}
return;
}
@@ -2482,29 +2430,31 @@
}
@GuardedBy("ImfLock.class")
- void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) {
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason,
+ @UserIdInt int userId) {
+ final var bindingController = getInputMethodBindingController(userId);
bindingController.setSelectedMethodId(null);
// Callback before clean-up binding states.
// TODO(b/338461930): Check if this is still necessary or not.
- onUnbindCurrentMethodByReset();
+ onUnbindCurrentMethodByReset(userId);
bindingController.unbindCurrentMethod();
- unbindCurrentClientLocked(unbindClientReason);
+ unbindCurrentClientLocked(unbindClientReason, userId);
}
@GuardedBy("ImfLock.class")
- void reRequestCurrentClientSessionLocked() {
- if (mCurClient != null) {
- clearClientSessionLocked(mCurClient);
- clearClientSessionForAccessibilityLocked(mCurClient);
- requestClientSessionLocked(mCurClient);
- requestClientSessionForAccessibilityLocked(mCurClient);
+ void reRequestCurrentClientSessionLocked(@UserIdInt int userId) {
+ final var userData = getUserData(userId);
+ if (userData.mCurClient != null) {
+ clearClientSessionLocked(userData.mCurClient);
+ clearClientSessionForAccessibilityLocked(userData.mCurClient);
+ requestClientSessionLocked(userData.mCurClient, userId);
+ requestClientSessionForAccessibilityLocked(userData.mCurClient);
}
}
@GuardedBy("ImfLock.class")
- void requestClientSessionLocked(ClientState cs) {
+ void requestClientSessionLocked(ClientState cs, @UserIdInt int userId) {
if (!cs.mSessionRequested) {
if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
final InputChannel serverChannel;
@@ -2517,14 +2467,15 @@
cs.mSessionRequested = true;
- final IInputMethodInvoker curMethod = getCurMethodLocked();
+ final var bindingController = getInputMethodBindingController(userId);
+ final IInputMethodInvoker curMethod = bindingController.getCurMethod();
final IInputMethodSessionCallback.Stub callback =
new IInputMethodSessionCallback.Stub() {
@Override
public void sessionCreated(IInputMethodSession session) {
final long ident = Binder.clearCallingIdentity();
try {
- onSessionCreated(curMethod, session, serverChannel);
+ onSessionCreated(curMethod, session, serverChannel, userId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -2615,25 +2566,34 @@
}
@GuardedBy("ImfLock.class")
- void clearClientSessionsLocked() {
- if (getCurMethodLocked() != null) {
+ void clearClientSessionsLocked(@NonNull InputMethodBindingController bindingController) {
+ final int userId = bindingController.mUserId;
+ final var userData = getUserData(userId);
+ if (bindingController.getCurMethod() != null) {
// TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
@SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession = c -> {
- clearClientSessionLocked(c);
- clearClientSessionForAccessibilityLocked(c);
+ // TODO(b/305849394): Figure out what we should do for single user IME mode.
+ final boolean shouldClearClientSession =
+ !mExperimentalConcurrentMultiUserModeEnabled
+ || UserHandle.getUserId(c.mUid) == userId;
+ if (shouldClearClientSession) {
+ clearClientSessionLocked(c);
+ clearClientSessionForAccessibilityLocked(c);
+ }
};
mClientController.forAllClients(clearClientSession);
- finishSessionLocked(mEnabledSession);
- for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) {
- finishSessionForAccessibilityLocked(mEnabledAccessibilitySessions.valueAt(i));
+ finishSessionLocked(userData.mEnabledSession);
+ for (int i = 0; i < userData.mEnabledAccessibilitySessions.size(); i++) {
+ finishSessionForAccessibilityLocked(
+ userData.mEnabledAccessibilitySessions.valueAt(i));
}
- mEnabledSession = null;
- mEnabledAccessibilitySessions.clear();
+ userData.mEnabledSession = null;
+ userData.mEnabledAccessibilitySessions.clear();
scheduleNotifyImeUidToAudioService(Process.INVALID_UID);
}
hideStatusBarIconLocked();
- mInFullscreenMode = false;
+ getUserData(userId).mInFullscreenMode = false;
mWindowManagerInternal.setDismissImeOnBackKeyPressed(false);
}
@@ -2857,6 +2817,7 @@
@GuardedBy("ImfLock.class")
private void updateSystemUiLocked(int vis, int backDisposition, @UserIdInt int userId) {
final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
final var curToken = bindingController.getCurToken();
if (curToken == null) {
return;
@@ -2868,8 +2829,8 @@
+ " inv: " + (vis & InputMethodService.IME_INVISIBLE)
+ " displayId: " + curTokenDisplayId);
}
- final IBinder focusedWindowToken = mImeBindingState != null
- ? mImeBindingState.mFocusedWindow : null;
+ final IBinder focusedWindowToken = userData.mImeBindingState != null
+ ? userData.mImeBindingState.mFocusedWindow : null;
final Boolean windowPerceptible = focusedWindowToken != null
? mFocusedWindowPerceptible.get(focusedWindowToken) : null;
@@ -2904,8 +2865,8 @@
}
@GuardedBy("ImfLock.class")
- void updateFromSettingsLocked(boolean enabledMayChange) {
- updateInputMethodsFromSettingsLocked(enabledMayChange);
+ void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
+ updateInputMethodsFromSettingsLocked(enabledMayChange, userId);
mMenuController.updateKeyboardFromSettingsLocked();
}
@@ -2915,7 +2876,7 @@
*
* <p>Never assume what this method is doing is officially supported. For the canonical and
* desired behaviors always refer to single-user code paths such as
- * {@link #updateInputMethodsFromSettingsLocked(boolean)}.</p>
+ * {@link #updateInputMethodsFromSettingsLocked(boolean, int)}.</p>
*
* <p>Here are examples of missing features.</p>
* <ul>
@@ -2964,8 +2925,7 @@
}
@GuardedBy("ImfLock.class")
- void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
- final int userId = mCurrentUserId;
+ void updateInputMethodsFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) {
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (enabledMayChange) {
final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext,
@@ -2996,7 +2956,7 @@
}
}
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ final var bindingController = getInputMethodBindingController(userId);
if (bindingController.getDeviceIdToShowIme() == DEVICE_ID_DEFAULT) {
String ime = SecureSettingsWrapper.getString(
Settings.Secure.DEFAULT_INPUT_METHOD, null, userId);
@@ -3026,14 +2986,14 @@
}
if (!TextUtils.isEmpty(id)) {
try {
- setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id));
+ setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeId(id), userId);
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
- resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED);
+ resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED, userId);
}
} else {
// There is no longer an input method set, so stop any current one.
- resetCurrentMethodAndClientLocked(UnbindReason.NO_IME);
+ resetCurrentMethodAndClientLocked(UnbindReason.NO_IME, userId);
}
final var userData = getUserData(userId);
@@ -3055,13 +3015,12 @@
}
@GuardedBy("ImfLock.class")
- void setInputMethodLocked(String id, int subtypeId) {
- setInputMethodLocked(id, subtypeId, DEVICE_ID_DEFAULT);
+ void setInputMethodLocked(String id, int subtypeId, @UserIdInt int userId) {
+ setInputMethodLocked(id, subtypeId, DEVICE_ID_DEFAULT, userId);
}
@GuardedBy("ImfLock.class")
- void setInputMethodLocked(String id, int subtypeId, int deviceId) {
- final int userId = mCurrentUserId;
+ void setInputMethodLocked(String id, int subtypeId, int deviceId, @UserIdInt int userId) {
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
InputMethodInfo info = settings.getMethodMap().get(id);
if (info == null) {
@@ -3096,8 +3055,8 @@
}
}
if (!Objects.equals(newSubtype, oldSubtype)) {
- setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
- IInputMethodInvoker curMethod = getCurMethodLocked();
+ setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true, userId);
+ IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null) {
updateSystemUiLocked(mImeWindowVis, mBackDisposition);
curMethod.changeInputMethodSubtype(newSubtype);
@@ -3116,7 +3075,7 @@
settings.putSelectedDefaultDeviceInputMethod(id);
return;
}
- IInputMethodInvoker curMethod = getCurMethodLocked();
+ IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null) {
curMethod.removeStylusHandwritingWindow();
}
@@ -3124,7 +3083,7 @@
try {
// Set a subtype to this input method.
// subtypeId the name of a subtype which will be set.
- setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
+ setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false, userId);
// mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
// because mCurMethodId is stored as a history in
// setSelectedInputMethodAndSubtypeLocked().
@@ -3136,7 +3095,7 @@
intent.putExtra("input_method_id", id);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
- unbindCurrentClientLocked(UnbindReason.SWITCH_IME);
+ unbindCurrentClientLocked(UnbindReason.SWITCH_IME, userId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3158,14 +3117,20 @@
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
return false;
}
+ // TODO(b/305849394): Create a utility method for the following policy.
+ final int userId = mExperimentalConcurrentMultiUserModeEnabled
+ ? UserHandle.getCallingUserId() : mCurrentUserId;
final long ident = Binder.clearCallingIdentity();
+ final var userData = getUserData(userId);
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
if (Flags.refactorInsetsController()) {
boolean wasVisible = isInputShownLocked();
- if (mImeBindingState != null && mImeBindingState.mFocusedWindowClient != null
- && mImeBindingState.mFocusedWindowClient.mClient != null) {
- mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(true);
+ if (userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindowClient != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
+ userData.mImeBindingState.mFocusedWindowClient.mClient
+ .setImeVisibility(true);
if (resultReceiver != null) {
resultReceiver.send(
wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN
@@ -3176,7 +3141,7 @@
return false;
} else {
return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType,
- resultReceiver, reason);
+ resultReceiver, reason, userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -3190,12 +3155,14 @@
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#showSoftInput", mDumper);
synchronized (ImfLock.class) {
+ // TODO(b/305849394): Infer userId from windowToken
+ final int userId = mCurrentUserId;
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
return showCurrentInputLocked(windowToken, null /* statsToken */, 0 /* flags */,
0 /* lastClickTooType */, null /* resultReceiver */,
- SoftInputShowHideReason.SHOW_SOFT_INPUT);
+ SoftInputShowHideReason.SHOW_SOFT_INPUT, userId);
} finally {
Binder.restoreCallingIdentity(ident);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3208,11 +3175,14 @@
ImeTracing.getInstance().triggerManagerServiceDump(
"InputMethodManagerService#hideSoftInput", mDumper);
synchronized (ImfLock.class) {
+ // TODO(b/305849394): Infer userId from windowToken
+ final int userId = mCurrentUserId;
final long ident = Binder.clearCallingIdentity();
try {
if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
return hideCurrentInputLocked(windowToken, null /* statsToken */, 0 /* flags */,
- null /* resultReceiver */, SoftInputShowHideReason.HIDE_SOFT_INPUT);
+ null /* resultReceiver */, SoftInputShowHideReason.HIDE_SOFT_INPUT,
+ userId);
} finally {
Binder.restoreCallingIdentity(ident);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -3342,7 +3312,7 @@
return false;
}
if (DEBUG) Slog.v(TAG, "Client requesting Stylus Handwriting to be started");
- final IInputMethodInvoker curMethod = getCurMethodLocked();
+ final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod != null) {
curMethod.canStartStylusHandwriting(requestId.getAsInt(),
connectionlessCallback, cursorAnchorInfo,
@@ -3414,8 +3384,9 @@
return false;
}
synchronized (ImfLock.class) {
+ final var bindingController = getInputMethodBindingController(userId);
if (mHwController.isDelegationUsingConnectionlessFlow()) {
- final IInputMethodInvoker curMethod = getCurMethodLocked();
+ final IInputMethodInvoker curMethod = bindingController.getCurMethod();
if (curMethod == null) {
return false;
}
@@ -3465,7 +3436,9 @@
Objects.requireNonNull(windowToken, "windowToken must not be null");
synchronized (ImfLock.class) {
Boolean windowPerceptible = mFocusedWindowPerceptible.get(windowToken);
- if (mImeBindingState.mFocusedWindow != windowToken
+ final int userId = mCurrentUserId;
+ final var userData = getUserData(userId);
+ if (userData.mImeBindingState.mFocusedWindow != windowToken
|| (windowPerceptible != null && windowPerceptible == perceptible)) {
return;
}
@@ -3477,17 +3450,18 @@
@GuardedBy("ImfLock.class")
private boolean showCurrentInputLocked(IBinder windowToken,
- @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) {
- final var statsToken = createStatsTokenForFocusedClient(true /* show */, reason);
+ @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
+ @UserIdInt int userId) {
+ final var statsToken = createStatsTokenForFocusedClient(true /* show */, reason, userId);
return showCurrentInputLocked(windowToken, statsToken, flags,
- MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason);
+ MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason, userId);
}
@GuardedBy("ImfLock.class")
boolean showCurrentInputLocked(IBinder windowToken,
@NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags,
@MotionEvent.ToolType int lastClickToolType, @Nullable ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
+ @SoftInputShowHideReason int reason, @UserIdInt int userId) {
if (!mVisibilityStateComputer.onImeShowFlags(statsToken, flags)) {
return false;
}
@@ -3500,22 +3474,24 @@
mVisibilityStateComputer.requestImeVisibility(windowToken, true);
- final int userId = mCurrentUserId;
// Ensure binding the connection when IME is going to show.
final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
bindingController.setCurrentMethodVisible();
final IInputMethodInvoker curMethod = bindingController.getCurMethod();
- ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
+ ImeTracker.forLogging().onCancelled(userData.mCurStatsToken,
+ ImeTracker.PHASE_SERVER_WAIT_IME);
final boolean readyToDispatchToIme;
if (Flags.deferShowSoftInputUntilSessionCreation()) {
readyToDispatchToIme =
- curMethod != null && mCurClient != null && mCurClient.mCurSession != null;
+ curMethod != null && userData.mCurClient != null
+ && userData.mCurClient.mCurSession != null;
} else {
readyToDispatchToIme = curMethod != null;
}
if (readyToDispatchToIme) {
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
- mCurStatsToken = null;
+ userData.mCurStatsToken = null;
if (Flags.useHandwritingListenerForTooltype()) {
maybeReportToolType();
@@ -3529,7 +3505,7 @@
return true;
} else {
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
- mCurStatsToken = statsToken;
+ userData.mCurStatsToken = statsToken;
}
return false;
}
@@ -3575,16 +3551,22 @@
}
return false;
}
+ // TODO(b/305849394): Create a utility method for the following policy.
+ final int userId = mExperimentalConcurrentMultiUserModeEnabled
+ ? UserHandle.getCallingUserId() : mCurrentUserId;
final long ident = Binder.clearCallingIdentity();
+ final var userData = getUserData(userId);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput");
if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
if (Flags.refactorInsetsController()) {
- if (mImeBindingState != null && mImeBindingState.mFocusedWindowClient != null
- && mImeBindingState.mFocusedWindowClient.mClient != null) {
+ if (userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindowClient != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
boolean wasVisible = isInputShownLocked();
// TODO add windowToken to interface
- mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(false);
+ userData.mImeBindingState.mFocusedWindowClient.mClient
+ .setImeVisibility(false);
if (resultReceiver != null) {
resultReceiver.send(wasVisible ? InputMethodManager.RESULT_HIDDEN
: InputMethodManager.RESULT_UNCHANGED_HIDDEN, null);
@@ -3594,7 +3576,7 @@
return false;
} else {
return InputMethodManagerService.this.hideCurrentInputLocked(windowToken,
- statsToken, flags, resultReceiver, reason);
+ statsToken, flags, resultReceiver, reason, userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -3607,23 +3589,27 @@
@IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD)
public void hideSoftInputFromServerForTest() {
synchronized (ImfLock.class) {
- hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
- SoftInputShowHideReason.HIDE_SOFT_INPUT);
+ // TODO(b/305849394): Get userId from caller.
+ final int userId = mCurrentUserId;
+ final var userData = getUserData(userId);
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */,
+ SoftInputShowHideReason.HIDE_SOFT_INPUT, userId);
}
}
@GuardedBy("ImfLock.class")
private boolean hideCurrentInputLocked(IBinder windowToken,
- @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) {
- final var statsToken = createStatsTokenForFocusedClient(false /* show */, reason);
+ @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
+ @UserIdInt int userId) {
+ final var statsToken = createStatsTokenForFocusedClient(false /* show */, reason, userId);
return hideCurrentInputLocked(windowToken, statsToken, flags, null /* resultReceiver */,
- reason);
+ reason, userId);
}
@GuardedBy("ImfLock.class")
boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
@InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver,
- @SoftInputShowHideReason int reason) {
+ @SoftInputShowHideReason int reason, @UserIdInt int userId) {
if (!mVisibilityStateComputer.canHideIme(statsToken, flags)) {
return false;
}
@@ -3636,8 +3622,8 @@
// since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only
// IMMS#InputShown indicates that the software keyboard is shown.
// TODO(b/246309664): Clean up IMMS#mImeWindowVis
- final int userId = mCurrentUserId;
final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
IInputMethodInvoker curMethod = bindingController.getCurMethod();
final boolean shouldHideSoftInput = curMethod != null
&& (isInputShownLocked() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
@@ -3657,8 +3643,9 @@
bindingController.setCurrentMethodNotVisible();
mVisibilityStateComputer.clearImeShowFlags();
// Cancel existing statsToken for show IME as we got a hide request.
- ImeTracker.forLogging().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
- mCurStatsToken = null;
+ ImeTracker.forLogging().onCancelled(userData.mCurStatsToken,
+ ImeTracker.PHASE_SERVER_WAIT_IME);
+ userData.mCurStatsToken = null;
return shouldHideSoftInput;
}
@@ -3728,7 +3715,7 @@
return new InputBindResult(
InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
null /* method */, null /* accessibilitySessions */, null /* channel */,
- getSelectedMethodIdLocked(),
+ bindingController.getSelectedMethodId(),
bindingController.getSequenceNumber(),
false /* isInputMethodSuppressingSpellChecker */);
}
@@ -3788,7 +3775,8 @@
final boolean shouldClearFlag =
mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid);
final boolean showForced = mVisibilityStateComputer.mShowForced;
- if (mImeBindingState.mFocusedWindow != windowToken
+ final var userData = getUserData(userId);
+ if (userData.mImeBindingState.mFocusedWindow != windowToken
&& showForced && shouldClearFlag) {
mVisibilityStateComputer.mShowForced = false;
}
@@ -3807,8 +3795,8 @@
Slog.w(TAG, "If you need to impersonate a foreground user/profile from"
+ " a background user, use EditorInfo.targetInputMethodUser with"
+ " INTERACT_ACROSS_USERS_FULL permission.");
- hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
- SoftInputShowHideReason.HIDE_INVALID_USER);
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
+ 0 /* flags */, SoftInputShowHideReason.HIDE_INVALID_USER, userId);
return InputBindResult.INVALID_USER;
}
@@ -3868,7 +3856,9 @@
+ " cs=" + cs);
}
- final boolean sameWindowFocused = mImeBindingState.mFocusedWindow == windowToken;
+ final int userId = bindingController.mUserId;
+ final var userData = getUserData(userId);
+ final boolean sameWindowFocused = userData.mImeBindingState.mFocusedWindow == windowToken;
final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0;
final boolean startInputByWinGainedFocus =
(startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0;
@@ -3900,7 +3890,7 @@
null, null, null, null, -1, false);
}
- mImeBindingState = new ImeBindingState(bindingController.mUserId, windowToken,
+ userData.mImeBindingState = new ImeBindingState(bindingController.mUserId, windowToken,
softInputMode, cs, editorInfo);
mFocusedWindowPerceptible.put(windowToken, true);
@@ -3931,16 +3921,17 @@
}
break;
}
- final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason());
- mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow, statsToken,
- imeVisRes.getState(), imeVisRes.getReason());
+ final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason(),
+ userId);
+ mVisibilityApplier.applyImeVisibility(userData.mImeBindingState.mFocusedWindow,
+ statsToken, imeVisRes.getState(), imeVisRes.getReason(), userId);
if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) {
// If focused display changed, we should unbind current method
// to make app window in previous display relayout after Ime
// window token removed.
// Note that we can trust client's display ID as long as it matches
// to the display ID obtained from the window.
- if (cs.mSelfReportedDisplayId != getCurTokenDisplayIdLocked()) {
+ if (cs.mSelfReportedDisplayId != bindingController.getCurTokenDisplayId()) {
bindingController.unbindCurrentMethod();
}
}
@@ -3961,8 +3952,11 @@
@GuardedBy("ImfLock.class")
private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName,
@Nullable ImeTracker.Token statsToken) {
- if (mCurClient == null || client == null
- || mCurClient.mClient.asBinder() != client.asBinder()) {
+ // TODO(b/305849394): Get userId from callers.
+ final int userId = mCurrentUserId;
+ final var userData = getUserData(userId);
+ if (userData.mCurClient == null || client == null
+ || userData.mCurClient.mClient.asBinder() != client.asBinder()) {
// We need to check if this is the current client with
// focus in the window manager, to allow this call to
// be made before input is started in it.
@@ -3972,7 +3966,7 @@
throw new IllegalArgumentException("unknown client " + client.asBinder());
}
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
- if (!isImeClientFocused(mImeBindingState.mFocusedWindow, cs)) {
+ if (!isImeClientFocused(userData.mImeBindingState.mFocusedWindow, cs)) {
Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client));
return false;
}
@@ -3984,14 +3978,18 @@
@GuardedBy("ImfLock.class")
private boolean canShowInputMethodPickerLocked(IInputMethodClient client) {
final int uid = Binder.getCallingUid();
- if (mImeBindingState.mFocusedWindowClient != null && client != null
- && mImeBindingState.mFocusedWindowClient.mClient.asBinder() == client.asBinder()) {
+ // TODO(b/305849394): Get userId from callers.
+ final int userId = mCurrentUserId;
+ final var userData = getUserData(userId);
+ if (userData.mImeBindingState.mFocusedWindowClient != null && client != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient.asBinder()
+ == client.asBinder()) {
return true;
}
- if (mCurrentUserId != UserHandle.getUserId(uid)) {
+ if (userId != UserHandle.getUserId(uid)) {
return false;
}
- final var curIntent = getInputMethodBindingController(mCurrentUserId).getCurIntent();
+ final var curIntent = getInputMethodBindingController(userId).getCurIntent();
if (curIntent != null && InputMethodUtils.checkIfPackageBelongsToUid(
mPackageManagerInternal, uid, curIntent.getComponent().getPackageName())) {
return true;
@@ -4008,11 +4006,14 @@
+ Binder.getCallingUid() + ": " + client);
return;
}
-
+ // TODO(b/305849394): Create a utility method for the following policy.
+ final int userId = mExperimentalConcurrentMultiUserModeEnabled
+ ? UserHandle.getCallingUserId() : mCurrentUserId;
+ final var userData = getUserData(userId);
// Always call subtype picker, because subtype picker is a superset of input method
// picker.
- final int displayId =
- (mCurClient != null) ? mCurClient.mSelfReportedDisplayId : DEFAULT_DISPLAY;
+ final int displayId = (userData.mCurClient != null)
+ ? userData.mCurClient.mSelfReportedDisplayId : DEFAULT_DISPLAY;
mHandler.obtainMessage(MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)
.sendToTarget();
}
@@ -4044,9 +4045,8 @@
}
@BinderThread
- private void setInputMethod(@NonNull IBinder token, String id) {
+ private void setInputMethod(@NonNull IBinder token, String id, @UserIdInt int userId) {
final int callingUid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(callingUid);
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
return;
@@ -4057,15 +4057,14 @@
imi.getPackageName(), callingUid, userId, settings)) {
throw getExceptionForUnknownImeId(id);
}
- setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID);
+ setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID, userId);
}
}
@BinderThread
private void setInputMethodAndSubtype(@NonNull IBinder token, String id,
- InputMethodSubtype subtype) {
+ InputMethodSubtype subtype, @UserIdInt int userId) {
final int callingUid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(callingUid);
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
return;
@@ -4078,20 +4077,19 @@
}
if (subtype != null) {
setInputMethodWithSubtypeIdLocked(token, id,
- SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()));
+ SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()), userId);
} else {
- setInputMethod(token, id);
+ setInputMethod(token, id, userId);
}
}
}
@BinderThread
- private boolean switchToPreviousInputMethod(@NonNull IBinder token) {
+ private boolean switchToPreviousInputMethod(@NonNull IBinder token, @UserIdInt int userId) {
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
return false;
}
- final int userId = mCurrentUserId;
final var bindingController = getInputMethodBindingController(userId);
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype();
@@ -4156,9 +4154,10 @@
if (!TextUtils.isEmpty(targetLastImiId)) {
if (DEBUG) {
Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
- + ", from: " + getSelectedMethodIdLocked() + ", " + subtypeId);
+ + ", from: " + bindingController.getSelectedMethodId() + ", "
+ + subtypeId);
}
- setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId);
+ setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId, userId);
return true;
} else {
return false;
@@ -4167,18 +4166,19 @@
}
@BinderThread
- private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme) {
+ private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme,
+ @UserIdInt int userId) {
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
return false;
}
- return switchToNextInputMethodLocked(token, onlyCurrentIme);
+ return switchToNextInputMethodLocked(token, onlyCurrentIme, userId);
}
}
@GuardedBy("ImfLock.class")
- private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme) {
- final int userId = mCurrentUserId;
+ private boolean switchToNextInputMethodLocked(@Nullable IBinder token, boolean onlyCurrentIme,
+ @UserIdInt int userId) {
final var bindingController = getInputMethodBindingController(userId);
final var currentImi = bindingController.getSelectedMethod();
final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
@@ -4188,17 +4188,17 @@
return false;
}
setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
- nextSubtype.mSubtypeId);
+ nextSubtype.mSubtypeId, userId);
return true;
}
@BinderThread
- private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token) {
+ private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token,
+ @UserIdInt int userId) {
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
return false;
}
- final int userId = mCurrentUserId;
final var bindingController = getInputMethodBindingController(userId);
final var currentImi = bindingController.getSelectedMethod();
final ImeSubtypeListItem nextSubtype = getUserData(userId).mSwitchingController
@@ -4260,7 +4260,8 @@
DirectBootAwareness.AUTO);
InputMethodSettingsRepository.put(userId, newSettings);
if (isCurrentUser) {
- postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */);
+ postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */,
+ userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4299,7 +4300,7 @@
if (mSettingsObserver != null) {
mSettingsObserver.mLastEnabled = settings.getEnabledInputMethodsStr();
}
- updateInputMethodsFromSettingsLocked(false /* enabledChanged */);
+ updateInputMethodsFromSettingsLocked(false /* enabledChanged */, userId);
}
}
} finally {
@@ -4318,7 +4319,8 @@
@Override
@Deprecated
public int getInputMethodWindowVisibleHeight(@NonNull IInputMethodClient client) {
- int callingUid = Binder.getCallingUid();
+ final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getCallingUserId();
return Binder.withCleanCallingIdentity(() -> {
final int curTokenDisplayId;
synchronized (ImfLock.class) {
@@ -4326,9 +4328,13 @@
"getInputMethodWindowVisibleHeight", null /* statsToken */)) {
return 0;
}
+ // TODO(b/305849394): Create a utility method for the following policy.
+ final int userId = mExperimentalConcurrentMultiUserModeEnabled
+ ? callingUserId : mCurrentUserId;
+ final var bindingController = getInputMethodBindingController(userId);
// This should probably use the caller's display id, but because this is unsupported
// and maintained only for compatibility, there's no point in fixing it.
- curTokenDisplayId = getCurTokenDisplayIdLocked();
+ curTokenDisplayId = bindingController.getCurTokenDisplayId();
}
return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId);
});
@@ -4593,27 +4599,29 @@
private void dumpDebug(ProtoOutputStream proto, long fieldId) {
synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ final int userId = mCurrentUserId;
+ final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
final long token = proto.start(fieldId);
- proto.write(CUR_METHOD_ID, getSelectedMethodIdLocked());
+ proto.write(CUR_METHOD_ID, bindingController.getSelectedMethodId());
proto.write(CUR_SEQ, bindingController.getSequenceNumber());
- proto.write(CUR_CLIENT, Objects.toString(mCurClient));
- mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
+ proto.write(CUR_CLIENT, Objects.toString(userData.mCurClient));
+ userData.mImeBindingState.dumpDebug(proto, mWindowManagerInternal);
proto.write(LAST_IME_TARGET_WINDOW_NAME,
mWindowManagerInternal.getWindowName(mLastImeTargetWindow));
proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(
- mImeBindingState.mFocusedWindowSoftInputMode));
- if (mCurEditorInfo != null) {
- mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
+ userData.mImeBindingState.mFocusedWindowSoftInputMode));
+ if (userData.mCurEditorInfo != null) {
+ userData.mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE);
}
proto.write(CUR_ID, bindingController.getCurId());
mVisibilityStateComputer.dumpDebug(proto, fieldId);
- proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode);
- proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked()));
- proto.write(CUR_TOKEN_DISPLAY_ID, getCurTokenDisplayIdLocked());
+ proto.write(IN_FULLSCREEN_MODE, userData.mInFullscreenMode);
+ proto.write(CUR_TOKEN, Objects.toString(bindingController.getCurToken()));
+ proto.write(CUR_TOKEN_DISPLAY_ID, bindingController.getCurTokenDisplayId());
proto.write(SYSTEM_READY, mSystemReady);
proto.write(HAVE_CONNECTION, bindingController.hasMainConnection());
- proto.write(BOUND_TO_METHOD, mBoundToMethod);
+ proto.write(BOUND_TO_METHOD, userData.mBoundToMethod);
proto.write(IS_INTERACTIVE, mIsInteractive);
proto.write(BACK_DISPOSITION, mBackDisposition);
proto.write(IME_WINDOW_VISIBILITY, mImeWindowVis);
@@ -4623,20 +4631,19 @@
}
@BinderThread
- private void notifyUserAction(@NonNull IBinder token) {
+ private void notifyUserAction(@NonNull IBinder token, @UserIdInt int userId) {
if (DEBUG) {
Slog.d(TAG, "Got the notification of a user action.");
}
synchronized (ImfLock.class) {
- if (getCurTokenLocked() != token) {
+ final var bindingController = getInputMethodBindingController(userId);
+ if (bindingController.getCurToken() != token) {
if (DEBUG) {
Slog.d(TAG, "Ignoring the user action notification from IMEs that are no longer"
+ " active.");
}
return;
}
- final int userId = mCurrentUserId;
- final var bindingController = getInputMethodBindingController(userId);
final InputMethodInfo imi = bindingController.getSelectedMethod();
if (imi != null) {
getUserData(userId).mSwitchingController.onUserActionLocked(imi,
@@ -4647,7 +4654,7 @@
@BinderThread
private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible,
- @NonNull ImeTracker.Token statsToken) {
+ @NonNull ImeTracker.Token statsToken, @UserIdInt int userId) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
synchronized (ImfLock.class) {
@@ -4659,10 +4666,10 @@
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(
- windowToken);
+ windowToken, userId);
mVisibilityApplier.applyImeVisibility(requestToken, statsToken,
setVisible ? ImeVisibilityStateComputer.STATE_SHOW_IME
- : ImeVisibilityStateComputer.STATE_HIDE_IME, mCurrentUserId);
+ : ImeVisibilityStateComputer.STATE_HIDE_IME, userId);
}
} finally {
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -4683,7 +4690,9 @@
}
@GuardedBy("ImfLock.class")
- private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) {
+ private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId,
+ @UserIdInt int userId) {
+ final var bindingController = getInputMethodBindingController(userId);
if (token == null) {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.WRITE_SECURE_SETTINGS)
@@ -4692,7 +4701,7 @@
"Using null token requires permission "
+ android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
- } else if (getCurTokenLocked() != token) {
+ } else if (bindingController.getCurToken() != token) {
Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
+ " token: " + token);
return;
@@ -4708,7 +4717,7 @@
final long ident = Binder.clearCallingIdentity();
try {
- setInputMethodLocked(id, subtypeId);
+ setInputMethodLocked(id, subtypeId, userId);
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -4721,17 +4730,20 @@
void onShowHideSoftInputRequested(boolean show, IBinder requestImeToken,
@SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken,
@UserIdInt int userId) {
- final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken);
+ final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(requestImeToken,
+ userId);
final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
final WindowManagerInternal.ImeTargetInfo info =
mWindowManagerInternal.onToggleImeRequested(
- show, mImeBindingState.mFocusedWindow, requestToken,
+ show, userData.mImeBindingState.mFocusedWindow, requestToken,
bindingController.getCurTokenDisplayId());
mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry(
- mImeBindingState.mFocusedWindowClient, mImeBindingState.mFocusedWindowEditorInfo,
- info.focusedWindowName, mImeBindingState.mFocusedWindowSoftInputMode, reason,
- mInFullscreenMode, info.requestWindowName, info.imeControlTargetName,
- info.imeLayerTargetName, info.imeSurfaceParentName));
+ userData.mImeBindingState.mFocusedWindowClient,
+ userData.mImeBindingState.mFocusedWindowEditorInfo,
+ info.focusedWindowName, userData.mImeBindingState.mFocusedWindowSoftInputMode,
+ reason, userData.mInFullscreenMode, info.requestWindowName,
+ info.imeControlTargetName, info.imeLayerTargetName, info.imeSurfaceParentName));
if (statsToken != null) {
mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName);
@@ -4740,7 +4752,8 @@
@BinderThread
private void hideMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
- @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason) {
+ @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason,
+ @UserIdInt int userId) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput");
synchronized (ImfLock.class) {
@@ -4749,21 +4762,23 @@
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
return;
}
+ final var userData = getUserData(userId);
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
final long ident = Binder.clearCallingIdentity();
try {
if (Flags.refactorInsetsController()) {
- mCurClient.mClient.setImeVisibility(false);
+ userData.mCurClient.mClient.setImeVisibility(false);
// TODO we will loose the flags here
- if (mImeBindingState != null
- && mImeBindingState.mFocusedWindowClient != null
- && mImeBindingState.mFocusedWindowClient.mClient != null) {
- mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(false);
+ if (userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindowClient != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
+ userData.mImeBindingState.mFocusedWindowClient.mClient
+ .setImeVisibility(false);
}
} else {
hideCurrentInputLocked(mLastImeTargetWindow, statsToken, flags,
- null /* resultReceiver */, reason);
+ null /* resultReceiver */, reason, userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4776,7 +4791,8 @@
@BinderThread
private void showMySoftInput(@NonNull IBinder token, @NonNull ImeTracker.Token statsToken,
- @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason) {
+ @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason,
+ @UserIdInt int userId) {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput");
synchronized (ImfLock.class) {
@@ -4785,21 +4801,24 @@
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
return;
}
+ final var userData = getUserData(userId);
ImeTracker.forLogging().onProgress(statsToken,
ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME);
final long ident = Binder.clearCallingIdentity();
try {
if (Flags.refactorInsetsController()) {
- mCurClient.mClient.setImeVisibility(false);
+ userData.mCurClient.mClient.setImeVisibility(false);
// TODO we will loose the flags here
- if (mImeBindingState != null
- && mImeBindingState.mFocusedWindowClient != null
- && mImeBindingState.mFocusedWindowClient.mClient != null) {
- mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(true);
+ if (userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindowClient != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
+ userData.mImeBindingState.mFocusedWindowClient.mClient
+ .setImeVisibility(true);
}
} else {
showCurrentInputLocked(mLastImeTargetWindow, statsToken, flags,
- MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason);
+ MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason,
+ userId);
}
} finally {
Binder.restoreCallingIdentity(ident);
@@ -4820,36 +4839,43 @@
void onApplyImeVisibilityFromComputer(IBinder windowToken, @NonNull ImeTracker.Token statsToken,
@NonNull ImeVisibilityResult result) {
synchronized (ImfLock.class) {
+ // TODO(b/305849394): Infer userId from windowToken
+ final int userId = mCurrentUserId;
mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(),
- result.getReason());
+ result.getReason(), userId);
}
}
@GuardedBy("ImfLock.class")
- void setEnabledSessionLocked(SessionState session) {
- if (mEnabledSession != session) {
- if (mEnabledSession != null && mEnabledSession.mSession != null) {
- if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
- mEnabledSession.mMethod.setSessionEnabled(mEnabledSession.mSession, false);
+ void setEnabledSessionLocked(SessionState session,
+ @NonNull UserDataRepository.UserData userData) {
+ if (userData.mEnabledSession != session) {
+ if (userData.mEnabledSession != null && userData.mEnabledSession.mSession != null) {
+ if (DEBUG) Slog.v(TAG, "Disabling: " + userData.mEnabledSession);
+ userData.mEnabledSession.mMethod.setSessionEnabled(
+ userData.mEnabledSession.mSession, false);
}
- mEnabledSession = session;
- if (mEnabledSession != null && mEnabledSession.mSession != null) {
- if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
- mEnabledSession.mMethod.setSessionEnabled(mEnabledSession.mSession, true);
+ userData.mEnabledSession = session;
+ if (userData.mEnabledSession != null && userData.mEnabledSession.mSession != null) {
+ if (DEBUG) Slog.v(TAG, "Enabling: " + userData.mEnabledSession);
+ userData.mEnabledSession.mMethod.setSessionEnabled(
+ userData.mEnabledSession.mSession, true);
}
}
}
@GuardedBy("ImfLock.class")
void setEnabledSessionForAccessibilityLocked(
- SparseArray<AccessibilitySessionState> accessibilitySessions) {
+ SparseArray<AccessibilitySessionState> accessibilitySessions,
+ @NonNull UserDataRepository.UserData userData) {
// mEnabledAccessibilitySessions could the same object as accessibilitySessions.
SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>();
- for (int i = 0; i < mEnabledAccessibilitySessions.size(); i++) {
- if (!accessibilitySessions.contains(mEnabledAccessibilitySessions.keyAt(i))) {
- AccessibilitySessionState sessionState = mEnabledAccessibilitySessions.valueAt(i);
+ for (int i = 0; i < userData.mEnabledAccessibilitySessions.size(); i++) {
+ if (!accessibilitySessions.contains(userData.mEnabledAccessibilitySessions.keyAt(i))) {
+ AccessibilitySessionState sessionState =
+ userData.mEnabledAccessibilitySessions.valueAt(i);
if (sessionState != null) {
- disabledSessions.append(mEnabledAccessibilitySessions.keyAt(i),
+ disabledSessions.append(userData.mEnabledAccessibilitySessions.keyAt(i),
sessionState.mSession);
}
}
@@ -4860,7 +4886,7 @@
}
SparseArray<IAccessibilityInputMethodSession> enabledSessions = new SparseArray<>();
for (int i = 0; i < accessibilitySessions.size(); i++) {
- if (!mEnabledAccessibilitySessions.contains(accessibilitySessions.keyAt(i))) {
+ if (!userData.mEnabledAccessibilitySessions.contains(accessibilitySessions.keyAt(i))) {
AccessibilitySessionState sessionState = accessibilitySessions.valueAt(i);
if (sessionState != null) {
enabledSessions.append(accessibilitySessions.keyAt(i), sessionState.mSession);
@@ -4871,7 +4897,7 @@
AccessibilityManagerInternal.get().setImeSessionEnabled(enabledSessions,
true);
}
- mEnabledAccessibilitySessions = accessibilitySessions;
+ userData.mEnabledAccessibilitySessions = accessibilitySessions;
}
@SuppressWarnings("unchecked")
@@ -4931,25 +4957,33 @@
case MSG_HIDE_ALL_INPUT_METHODS:
synchronized (ImfLock.class) {
+ // TODO(b/305849394): Needs to figure out what to do where for background users.
+ final int userId = mCurrentUserId;
+ final var userData = getUserData(userId);
if (Flags.refactorInsetsController()) {
- if (mImeBindingState != null
- && mImeBindingState.mFocusedWindowClient != null
- && mImeBindingState.mFocusedWindowClient.mClient != null) {
- mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(false);
+ if (userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindowClient != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient != null) {
+ userData.mImeBindingState.mFocusedWindowClient.mClient
+ .setImeVisibility(false);
}
} else {
@SoftInputShowHideReason final int reason = (int) msg.obj;
- hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
- reason);
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
+ 0 /* flags */, reason, userId);
}
}
return true;
case MSG_REMOVE_IME_SURFACE: {
synchronized (ImfLock.class) {
+ // TODO(b/305849394): Needs to figure out what to do where for background users.
+ final int userId = mCurrentUserId;
+ final var userData = getUserData(userId);
try {
- if (mEnabledSession != null && mEnabledSession.mSession != null
- && !isShowRequestedForCurrentWindow()) {
- mEnabledSession.mSession.removeImeSurface();
+ if (userData.mEnabledSession != null
+ && userData.mEnabledSession.mSession != null
+ && !isShowRequestedForCurrentWindow(userId)) {
+ userData.mEnabledSession.mSession.removeImeSurface();
}
} catch (RemoteException e) {
}
@@ -4959,10 +4993,14 @@
case MSG_REMOVE_IME_SURFACE_FROM_WINDOW: {
IBinder windowToken = (IBinder) msg.obj;
synchronized (ImfLock.class) {
+ // TODO(b/305849394): Infer userId from windowToken.
+ final int userId = mCurrentUserId;
+ final var userData = getUserData(userId);
try {
- if (windowToken == mImeBindingState.mFocusedWindow
- && mEnabledSession != null && mEnabledSession.mSession != null) {
- mEnabledSession.mSession.removeImeSurface();
+ if (windowToken == userData.mImeBindingState.mFocusedWindow
+ && userData.mEnabledSession != null
+ && userData.mEnabledSession.mSession != null) {
+ userData.mEnabledSession.mSession.removeImeSurface();
}
} catch (RemoteException e) {
}
@@ -5015,9 +5053,11 @@
synchronized (ImfLock.class) {
final var bindingController = getInputMethodBindingController(mCurrentUserId);
if (bindingController.supportsStylusHandwriting()
- && getCurMethodLocked() != null && hasSupportedStylusLocked()) {
+ && bindingController.getCurMethod() != null
+ && hasSupportedStylusLocked()) {
Slog.d(TAG, "Initializing Handwriting Spy");
- mHwController.initializeHandwritingSpy(getCurTokenDisplayIdLocked());
+ mHwController.initializeHandwritingSpy(
+ bindingController.getCurTokenDisplayId());
} else {
mHwController.reset();
}
@@ -5034,18 +5074,21 @@
}
return true;
case MSG_START_HANDWRITING:
+ final var handwritingRequest = (HandwritingRequest) msg.obj;
synchronized (ImfLock.class) {
- IInputMethodInvoker curMethod = getCurMethodLocked();
- if (curMethod == null || mImeBindingState.mFocusedWindow == null) {
+ final int userId = handwritingRequest.userId;
+ final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
+ IInputMethodInvoker curMethod = bindingController.getCurMethod();
+ if (curMethod == null || userData.mImeBindingState.mFocusedWindow == null) {
return true;
}
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
final HandwritingModeController.HandwritingSession session =
mHwController.startHandwritingSession(
- msg.arg1 /*requestId*/,
- msg.arg2 /*pid*/,
+ handwritingRequest.requestId,
+ handwritingRequest.pid,
bindingController.getCurMethodUid(),
- mImeBindingState.mFocusedWindow);
+ userData.mImeBindingState.mFocusedWindow);
if (session == null) {
Slog.e(TAG,
"Failed to start handwriting session for requestId: " + msg.arg1);
@@ -5080,9 +5123,12 @@
return false;
}
+ private record HandwritingRequest(int requestId, int pid, @UserIdInt int userId) { }
+
@BinderThread
- private void onStylusHandwritingReady(int requestId, int pid) {
- mHandler.obtainMessage(MSG_START_HANDWRITING, requestId, pid).sendToTarget();
+ private void onStylusHandwritingReady(int requestId, int pid, @UserIdInt int userId) {
+ mHandler.obtainMessage(MSG_START_HANDWRITING,
+ new HandwritingRequest(requestId, pid, userId)).sendToTarget();
}
private void handleSetInteractive(final boolean interactive) {
@@ -5090,8 +5136,11 @@
mIsInteractive = interactive;
updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition);
+ // TODO(b/305849394): Support multiple IMEs.
+ final var userId = mCurrentUserId;
+ final var userData = getUserData(userId);
// Inform the current client of the change in active status
- if (mCurClient == null || mCurClient.mClient == null) {
+ if (userData.mCurClient == null || userData.mCurClient.mClient == null) {
return;
}
// TODO(b/325515685): user data must be retrieved by a userId parameter
@@ -5101,17 +5150,19 @@
// Handle IME visibility when interactive changed before finishing the input to
// ensure we preserve the last state as possible.
final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.onInteractiveChanged(
- mImeBindingState.mFocusedWindow, interactive);
+ userData.mImeBindingState.mFocusedWindow, interactive);
if (imeVisRes != null) {
// Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker.
- mVisibilityApplier.applyImeVisibility(mImeBindingState.mFocusedWindow,
- null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason());
+ mVisibilityApplier.applyImeVisibility(userData.mImeBindingState.mFocusedWindow,
+ null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason(),
+ userId);
}
// Eligible IME processes use new "setInteractive" protocol.
- mCurClient.mClient.setInteractive(mIsInteractive, mInFullscreenMode);
+ userData.mCurClient.mClient.setInteractive(mIsInteractive,
+ userData.mInFullscreenMode);
} else {
// Legacy IME processes continue using legacy "setActive" protocol.
- mCurClient.mClient.setActive(mIsInteractive, mInFullscreenMode);
+ userData.mCurClient.mClient.setActive(mIsInteractive, userData.mInFullscreenMode);
}
}
}
@@ -5228,7 +5279,8 @@
}
@GuardedBy("ImfLock.class")
- void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme) {
+ void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme,
+ @UserIdInt int userId) {
if (DEBUG) {
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+ " \n ------ caller=" + Debug.getCallers(10));
@@ -5238,7 +5290,6 @@
return;
}
- final int userId = mCurrentUserId;
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
boolean reenableMinimumNonAuxSystemImes = false;
@@ -5291,7 +5342,7 @@
if (!settings.getMethodMap().containsKey(defaultImiId)) {
Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
if (chooseNewDefaultIMELocked()) {
- updateInputMethodsFromSettingsLocked(true);
+ updateInputMethodsFromSettingsLocked(true, userId);
}
} else {
// Double check that the default IME is certainly enabled.
@@ -5417,11 +5468,10 @@
@GuardedBy("ImfLock.class")
private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
- boolean setSubtypeOnly) {
- final int userId = mCurrentUserId;
+ boolean setSubtypeOnly, @UserIdInt int userId) {
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
final var bindingController = getInputMethodBindingController(userId);
- settings.saveCurrentInputMethodAndSubtypeToHistory(getSelectedMethodIdLocked(),
+ settings.saveCurrentInputMethodAndSubtypeToHistory(bindingController.getSelectedMethodId(),
bindingController.getCurrentSubtype());
// Set Subtype here
@@ -5455,11 +5505,13 @@
@GuardedBy("ImfLock.class")
private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ // TODO(b/305849394): get userId from callers
+ final int userId = mCurrentUserId;
+ final var bindingController = getInputMethodBindingController(userId);
bindingController.setDisplayIdToShowIme(INVALID_DISPLAY);
bindingController.setDeviceIdToShowIme(DEVICE_ID_DEFAULT);
- final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
settings.putSelectedDefaultDeviceInputMethod(null);
InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme);
@@ -5476,7 +5528,7 @@
}
}
}
- setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
+ setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false, userId);
}
/**
@@ -5560,7 +5612,7 @@
.contains(settings.getMethodMap().get(imeId))) {
return false; // IME is not found or not enabled.
}
- setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
+ setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID, userId);
return true;
}
if (!settings.getMethodMap().containsKey(imeId)
@@ -5602,15 +5654,15 @@
}
@GuardedBy("ImfLock.class")
- private void switchKeyboardLayoutLocked(int direction) {
- final int userId = mCurrentUserId;
+ private void switchKeyboardLayoutLocked(int direction, @UserIdInt int userId) {
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
- final InputMethodInfo currentImi = settings.getMethodMap().get(getSelectedMethodIdLocked());
+ final var bindingController = getInputMethodBindingController(userId);
+ final InputMethodInfo currentImi = settings.getMethodMap().get(
+ bindingController.getSelectedMethodId());
if (currentImi == null) {
return;
}
- final var bindingController = getInputMethodBindingController(userId);
final InputMethodSubtypeHandle currentSubtypeHandle =
InputMethodSubtypeHandle.of(currentImi, bindingController.getCurrentSubtype());
final InputMethodSubtypeHandle nextSubtypeHandle =
@@ -5627,7 +5679,7 @@
final int subtypeCount = nextImi.getSubtypeCount();
if (subtypeCount == 0) {
if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
- setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
+ setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID, userId);
}
return;
}
@@ -5635,7 +5687,7 @@
for (int i = 0; i < subtypeCount; ++i) {
if (nextSubtypeHandle.equals(
InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
- setInputMethodLocked(nextImi.getId(), i);
+ setInputMethodLocked(nextImi.getId(), i, userId);
return;
}
}
@@ -5752,7 +5804,8 @@
//TODO(b/150843766): Check if Input Token is valid.
final IBinder curHostInputToken;
synchronized (ImfLock.class) {
- if (displayId != getCurTokenDisplayIdLocked()) {
+ final var bindingController = getInputMethodBindingController(userId);
+ if (displayId != bindingController.getCurTokenDisplayId()) {
return false;
}
curHostInputToken = getInputMethodBindingController(userId).getCurHostInputToken();
@@ -5766,7 +5819,10 @@
@Override
public void reportImeControl(@Nullable IBinder windowToken) {
synchronized (ImfLock.class) {
- if (mImeBindingState.mFocusedWindow != windowToken) {
+ // TODO(b/305849394): Need to infer userId or get userId from callers.
+ final int userId = mCurrentUserId;
+ final var userData = getUserData(userId);
+ if (userData.mImeBindingState.mFocusedWindow != windowToken) {
// A perceptible value was set for the focused window, but it is no longer in
// control, so we reset the perceptible for the window passed as argument.
// TODO(b/314149476): Investigate whether this logic is still relevant, if not
@@ -5779,10 +5835,13 @@
@Override
public void onImeParentChanged(int displayId) {
synchronized (ImfLock.class) {
+ // TODO(b/305849394): Need to infer userId or get userId from callers.
+ final int userId = mCurrentUserId;
+ final var userData = getUserData(userId);
// Hide the IME method menu only when the IME surface parent is changed by the
// input target changed, in case seeing the dialog dismiss flickering during
// the next focused window starting the input connection.
- if (mLastImeTargetWindow != mImeBindingState.mFocusedWindow) {
+ if (mLastImeTargetWindow != userData.mImeBindingState.mFocusedWindow) {
mMenuController.hideInputMethodMenuLocked();
}
}
@@ -5805,33 +5864,36 @@
public void onSessionForAccessibilityCreated(int accessibilityConnectionId,
IAccessibilityInputMethodSession session, @UserIdInt int userId) {
synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
// TODO(b/305829876): Implement user ID verification
- if (mCurClient != null) {
- clearClientSessionForAccessibilityLocked(mCurClient, accessibilityConnectionId);
- mCurClient.mAccessibilitySessions.put(
+ if (userData.mCurClient != null) {
+ clearClientSessionForAccessibilityLocked(userData.mCurClient,
+ accessibilityConnectionId);
+ userData.mCurClient.mAccessibilitySessions.put(
accessibilityConnectionId,
- new AccessibilitySessionState(mCurClient,
+ new AccessibilitySessionState(userData.mCurClient,
accessibilityConnectionId,
session));
attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY,
- true);
+ true, userId);
- final SessionState sessionState = mCurClient.mCurSession;
+ final SessionState sessionState = userData.mCurClient.mCurSession;
final IInputMethodSession imeSession = sessionState == null
? null : sessionState.mSession;
final SparseArray<IAccessibilityInputMethodSession>
accessibilityInputMethodSessions =
createAccessibilityInputMethodSessions(
- mCurClient.mAccessibilitySessions);
+ userData.mCurClient.mAccessibilitySessions);
final InputBindResult res = new InputBindResult(
InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION,
imeSession, accessibilityInputMethodSessions, /* channel= */ null,
bindingController.getCurId(),
bindingController.getSequenceNumber(),
/* isInputMethodSuppressingSpellChecker= */ false);
- mCurClient.mClient.onBindAccessibilityService(res, accessibilityConnectionId);
+ userData.mCurClient.mClient.onBindAccessibilityService(res,
+ accessibilityConnectionId);
}
}
}
@@ -5840,33 +5902,34 @@
public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId,
@UserIdInt int userId) {
synchronized (ImfLock.class) {
- final var bindingController = getInputMethodBindingController(mCurrentUserId);
+ final var bindingController = getInputMethodBindingController(userId);
+ final var userData = getUserData(userId);
// TODO(b/305829876): Implement user ID verification
- if (mCurClient != null) {
+ if (userData.mCurClient != null) {
if (DEBUG) {
Slog.v(TAG, "unbindAccessibilityFromCurrentClientLocked: client="
- + mCurClient.mClient.asBinder());
+ + userData.mCurClient.mClient.asBinder());
}
// A11yManagerService unbinds the disabled accessibility service. We don't need
// to do it here.
- mCurClient.mClient.onUnbindAccessibilityService(
+ userData.mCurClient.mClient.onUnbindAccessibilityService(
bindingController.getSequenceNumber(),
accessibilityConnectionId);
}
// We only have sessions when we bound to an input method. Remove this session
// from all clients.
- if (getCurMethodLocked() != null) {
+ if (bindingController.getCurMethod() != null) {
// TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed.
@SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession =
c -> clearClientSessionForAccessibilityLocked(c,
accessibilityConnectionId);
mClientController.forAllClients(clearClientSession);
- AccessibilitySessionState session = mEnabledAccessibilitySessions.get(
+ AccessibilitySessionState session = userData.mEnabledAccessibilitySessions.get(
accessibilityConnectionId);
if (session != null) {
finishSessionForAccessibilityLocked(session);
- mEnabledAccessibilitySessions.remove(accessibilityConnectionId);
+ userData.mEnabledAccessibilitySessions.remove(accessibilityConnectionId);
}
}
}
@@ -5883,14 +5946,15 @@
public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
IBinder targetWindowToken) {
synchronized (ImfLock.class) {
- switchKeyboardLayoutLocked(direction);
+ // TODO(b/305849394): Infer userId from displayId
+ switchKeyboardLayoutLocked(direction, mCurrentUserId);
}
}
}
@BinderThread
private IInputContentUriToken createInputContentUriToken(@Nullable IBinder token,
- @Nullable Uri contentUri, @Nullable String packageName) {
+ @Nullable Uri contentUri, @Nullable String packageName, @UserIdInt int imeUserId) {
if (token == null) {
throw new NullPointerException("token");
}
@@ -5907,15 +5971,6 @@
synchronized (ImfLock.class) {
final int uid = Binder.getCallingUid();
- final int imeUserId = UserHandle.getUserId(uid);
- if (imeUserId != mCurrentUserId) {
- // Currently concurrent multi-user is not supported here due to the remaining
- // dependency on mCurEditorInfo and mCurClient.
- // TODO(b/341558132): Remove this early-exit once it becomes multi-user ready.
- Slog.i(TAG, "Ignoring createInputContentUriToken due to user ID mismatch."
- + " imeUserId=" + imeUserId + " mCurrentUserId=" + mCurrentUserId);
- return null;
- }
final var bindingController = getInputMethodBindingController(imeUserId);
if (bindingController.getSelectedMethodId() == null) {
return null;
@@ -5928,16 +5983,16 @@
// We cannot simply distinguish a bad IME that reports an arbitrary package name from
// an unfortunate IME whose internal state is already obsolete due to the asynchronous
// nature of our system. Let's compare it with our internal record.
- // TODO(b/341558132): Use "imeUserId" to query per-user "curEditorInfo"
- final var curPackageName = mCurEditorInfo != null ? mCurEditorInfo.packageName : null;
+ final var userData = getUserData(imeUserId);
+ final var curPackageName = userData.mCurEditorInfo != null
+ ? userData.mCurEditorInfo.packageName : null;
if (!TextUtils.equals(curPackageName, packageName)) {
Slog.e(TAG, "Ignoring createInputContentUriToken mCurEditorInfo.packageName="
+ curPackageName + " packageName=" + packageName);
return null;
}
// This user ID can never be spoofed.
- // TODO(b/341558132): Use "imeUserId" to query per-user "curClient"
- final int appUserId = UserHandle.getUserId(mCurClient.mUid);
+ final int appUserId = UserHandle.getUserId(userData.mCurClient.mUid);
// This user ID may be invalid if "contentUri" embedded an invalid user ID.
final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
imeUserId);
@@ -5954,14 +6009,16 @@
}
@BinderThread
- private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen) {
+ private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen,
+ @UserIdInt int userId) {
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
return;
}
- if (mCurClient != null && mCurClient.mClient != null) {
- mInFullscreenMode = fullscreen;
- mCurClient.mClient.reportFullscreenMode(fullscreen);
+ final var userData = getUserData(userId);
+ if (userData.mCurClient != null && userData.mCurClient.mClient != null) {
+ userData.mInFullscreenMode = fullscreen;
+ userData.mCurClient.mClient.reportFullscreenMode(fullscreen);
}
}
}
@@ -6054,7 +6111,9 @@
final Printer p = new PrintWriterPrinter(pw);
synchronized (ImfLock.class) {
- final InputMethodSettings settings = InputMethodSettingsRepository.get(mCurrentUserId);
+ final int userId = mCurrentUserId;
+ final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
+ final var userData = getUserData(userId);
p.println("Current Input Method Manager state:");
final List<InputMethodInfo> methodList = settings.getMethodList();
int numImes = methodList.size();
@@ -6084,16 +6143,16 @@
mClientController.forAllClients(clientControllerDump);
final var bindingController = getInputMethodBindingController(mCurrentUserId);
p.println(" mCurrentUserId=" + mCurrentUserId);
- p.println(" mCurMethodId=" + getSelectedMethodIdLocked());
- client = mCurClient;
+ p.println(" mCurMethodId=" + bindingController.getSelectedMethodId());
+ client = userData.mCurClient;
p.println(" mCurClient=" + client + " mCurSeq="
+ bindingController.getSequenceNumber());
p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible);
- mImeBindingState.dump(/* prefix= */ " ", p);
+ userData.mImeBindingState.dump(/* prefix= */ " ", p);
p.println(" mCurId=" + bindingController.getCurId()
+ " mHaveConnection=" + bindingController.hasMainConnection()
- + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound="
+ + " mBoundToMethod=" + userData.mBoundToMethod + " mVisibleBound="
+ bindingController.isVisibleBound());
p.println(" mUserDataRepository=");
@@ -6109,15 +6168,15 @@
};
mUserDataRepository.forAllUserData(userDataDump);
- p.println(" mCurToken=" + getCurTokenLocked());
- p.println(" mCurTokenDisplayId=" + getCurTokenDisplayIdLocked());
+ p.println(" mCurToken=" + bindingController.getCurToken());
+ p.println(" mCurTokenDisplayId=" + bindingController.getCurTokenDisplayId());
p.println(" mCurHostInputToken=" + bindingController.getCurHostInputToken());
p.println(" mCurIntent=" + bindingController.getCurIntent());
- method = getCurMethodLocked();
- p.println(" mCurMethod=" + getCurMethodLocked());
- p.println(" mEnabledSession=" + mEnabledSession);
+ method = bindingController.getCurMethod();
+ p.println(" mCurMethod=" + method);
+ p.println(" mEnabledSession=" + userData.mEnabledSession);
mVisibilityStateComputer.dump(pw, " ");
- p.println(" mInFullscreenMode=" + mInFullscreenMode);
+ p.println(" mInFullscreenMode=" + userData.mInFullscreenMode);
p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
p.println(" mExperimentalConcurrentMultiUserModeEnabled="
+ mExperimentalConcurrentMultiUserModeEnabled);
@@ -6153,20 +6212,24 @@
} else {
p.println("No input method client.");
}
-
- if (mImeBindingState.mFocusedWindowClient != null
- && client != mImeBindingState.mFocusedWindowClient) {
- p.println(" ");
- p.println("Warning: Current input method client doesn't match the last focused. "
- + "window.");
- p.println("Dumping input method client in the last focused window just in case.");
- p.println(" ");
- pw.flush();
- try {
- TransferPipe.dumpAsync(
- mImeBindingState.mFocusedWindowClient.mClient.asBinder(), fd, args);
- } catch (IOException | RemoteException e) {
- p.println("Failed to dump input method client in focused window: " + e);
+ synchronized (ImfLock.class) {
+ final int userId = mCurrentUserId;
+ final var userData = getUserData(userId);
+ if (userData.mImeBindingState.mFocusedWindowClient != null
+ && client != userData.mImeBindingState.mFocusedWindowClient) {
+ p.println(" ");
+ p.println("Warning: Current input method client doesn't match the last focused. "
+ + "window.");
+ p.println("Dumping input method client in the last focused window just in case.");
+ p.println(" ");
+ pw.flush();
+ try {
+ TransferPipe.dumpAsync(
+ userData.mImeBindingState.mFocusedWindowClient.mClient.asBinder(), fd,
+ args);
+ } catch (IOException | RemoteException e) {
+ p.println("Failed to dump input method client in focused window: " + e);
+ }
}
}
@@ -6602,18 +6665,21 @@
final List<InputMethodInfo> nextEnabledImes;
final InputMethodSettings settings = InputMethodSettingsRepository.get(userId);
if (userId == mCurrentUserId) {
+ final var userData = getUserData(userId);
if (Flags.refactorInsetsController()) {
- if (mImeBindingState != null
- && mImeBindingState.mFocusedWindowClient != null
- && mImeBindingState.mFocusedWindowClient.mClient != null) {
- mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(
- false);
+ if (userData.mImeBindingState != null
+ && userData.mImeBindingState.mFocusedWindowClient != null
+ && userData.mImeBindingState.mFocusedWindowClient.mClient
+ != null) {
+ userData.mImeBindingState.mFocusedWindowClient.mClient
+ .setImeVisibility(false);
} else {
// TODO(b329229469): ImeTracker?
}
} else {
- hideCurrentInputLocked(mImeBindingState.mFocusedWindow, 0 /* flags */,
- SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND);
+ hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow,
+ 0 /* flags */,
+ SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND, userId);
}
final var bindingController = getInputMethodBindingController(userId);
bindingController.unbindCurrentMethod();
@@ -6633,7 +6699,7 @@
if (!chooseNewDefaultIMELocked()) {
resetSelectedInputMethodAndSubtypeLocked(null);
}
- updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
+ updateInputMethodsFromSettingsLocked(true /* enabledMayChange */, userId);
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, settings.getUserId()),
settings.getEnabledInputMethodList());
@@ -6742,13 +6808,15 @@
* @param reason the reason why the IME request was created
*/
@NonNull
+ @GuardedBy("ImfLock.class")
private ImeTracker.Token createStatsTokenForFocusedClient(boolean show,
- @SoftInputShowHideReason int reason) {
- final int uid = mImeBindingState.mFocusedWindowClient != null
- ? mImeBindingState.mFocusedWindowClient.mUid
+ @SoftInputShowHideReason int reason, @UserIdInt int userId) {
+ final var userData = getUserData(userId);
+ final int uid = userData.mImeBindingState.mFocusedWindowClient != null
+ ? userData.mImeBindingState.mFocusedWindowClient.mUid
: -1;
- final var packageName = mImeBindingState.mFocusedWindowEditorInfo != null
- ? mImeBindingState.mFocusedWindowEditorInfo.packageName
+ final var packageName = userData.mImeBindingState.mFocusedWindowEditorInfo != null
+ ? userData.mImeBindingState.mFocusedWindowEditorInfo.packageName
: "uid(" + uid + ")";
return ImeTracker.forLogging().onStart(packageName, uid,
@@ -6761,11 +6829,14 @@
private final InputMethodManagerService mImms;
@NonNull
private final IBinder mToken;
+ @UserIdInt
+ private final int mUserId;
InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms,
- @NonNull IBinder token) {
+ @NonNull IBinder token, @UserIdInt int userId) {
mImms = imms;
mToken = token;
+ mUserId = userId;
}
@BinderThread
@@ -6793,7 +6864,7 @@
@SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future;
try {
typedFuture.complete(mImms.createInputContentUriToken(
- mToken, contentUri, packageName).asBinder());
+ mToken, contentUri, packageName, mUserId).asBinder());
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6802,7 +6873,7 @@
@BinderThread
@Override
public void reportFullscreenModeAsync(boolean fullscreen) {
- mImms.reportFullscreenMode(mToken, fullscreen);
+ mImms.reportFullscreenMode(mToken, fullscreen, mUserId);
}
@BinderThread
@@ -6810,7 +6881,7 @@
public void setInputMethod(String id, AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.setInputMethod(mToken, id);
+ mImms.setInputMethod(mToken, id, mUserId);
typedFuture.complete(null);
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
@@ -6823,7 +6894,7 @@
AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.setInputMethodAndSubtype(mToken, id, subtype);
+ mImms.setInputMethodAndSubtype(mToken, id, subtype, mUserId);
typedFuture.complete(null);
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
@@ -6837,7 +6908,7 @@
AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.hideMySoftInput(mToken, statsToken, flags, reason);
+ mImms.hideMySoftInput(mToken, statsToken, flags, reason, mUserId);
typedFuture.complete(null);
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
@@ -6851,7 +6922,7 @@
AndroidFuture future /* T=Void */) {
@SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future;
try {
- mImms.showMySoftInput(mToken, statsToken, flags, reason);
+ mImms.showMySoftInput(mToken, statsToken, flags, reason, mUserId);
typedFuture.complete(null);
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
@@ -6869,7 +6940,7 @@
public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) {
@SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
- typedFuture.complete(mImms.switchToPreviousInputMethod(mToken));
+ typedFuture.complete(mImms.switchToPreviousInputMethod(mToken, mUserId));
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6881,7 +6952,8 @@
AndroidFuture future /* T=Boolean */) {
@SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
- typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme));
+ typedFuture.complete(mImms.switchToNextInputMethod(mToken, onlyCurrentIme,
+ mUserId));
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6892,7 +6964,7 @@
public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) {
@SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future;
try {
- typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken));
+ typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethod(mToken, mUserId));
} catch (Throwable e) {
typedFuture.completeExceptionally(e);
}
@@ -6901,20 +6973,20 @@
@BinderThread
@Override
public void notifyUserActionAsync() {
- mImms.notifyUserAction(mToken);
+ mImms.notifyUserAction(mToken, mUserId);
}
@BinderThread
@Override
public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible,
@NonNull ImeTracker.Token statsToken) {
- mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken);
+ mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken, mUserId);
}
@BinderThread
@Override
public void onStylusHandwritingReady(int requestId, int pid) {
- mImms.onStylusHandwritingReady(requestId, pid);
+ mImms.onStylusHandwritingReady(requestId, pid, mUserId);
}
@BinderThread
@@ -6932,7 +7004,7 @@
}
final long ident = Binder.clearCallingIdentity();
try {
- mImms.switchKeyboardLayoutLocked(direction);
+ mImms.switchKeyboardLayoutLocked(direction, mUserId);
} finally {
Binder.restoreCallingIdentity(ident);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 326ef7e..89a31e7 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -79,6 +79,7 @@
if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
final int userId = mService.getCurrentImeUserIdLocked();
+ final var bindingController = mService.getInputMethodBindingController(userId);
hideInputMethodMenuLocked();
@@ -86,9 +87,9 @@
final InputMethodSubtype currentSubtype =
mService.getCurrentInputMethodSubtypeLocked();
if (currentSubtype != null) {
- final String curMethodId = mService.getSelectedMethodIdLocked();
+ final String curMethodId = bindingController.getSelectedMethodId();
final InputMethodInfo currentImi =
- mService.queryInputMethodForCurrentUserLocked(curMethodId);
+ InputMethodSettingsRepository.get(userId).getMethodMap().get(curMethodId);
preferredInputMethodSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(
currentImi, currentSubtype.hashCode());
}
@@ -179,7 +180,7 @@
if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
subtypeId = NOT_A_SUBTYPE_ID;
}
- mService.setInputMethodLocked(im.getId(), subtypeId);
+ mService.setInputMethodLocked(im.getId(), subtypeId, userId);
}
hideInputMethodMenuLocked();
}
diff --git a/services/core/java/com/android/server/inputmethod/UserDataRepository.java b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
index 3da4a14..48284fb 100644
--- a/services/core/java/com/android/server/inputmethod/UserDataRepository.java
+++ b/services/core/java/com/android/server/inputmethod/UserDataRepository.java
@@ -17,12 +17,18 @@
package com.android.server.inputmethod;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.UserInfo;
import android.os.Handler;
import android.util.SparseArray;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ImeTracker;
+import android.window.ImeOnBackInvokedDispatcher;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
import com.android.server.pm.UserManagerInternal;
import java.util.function.Consumer;
@@ -96,6 +102,78 @@
final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController;
/**
+ * Have we called mCurMethod.bindInput()?
+ */
+ @GuardedBy("ImfLock.class")
+ boolean mBoundToMethod = false;
+
+ /**
+ * Have we called bindInput() for accessibility services?
+ */
+ @GuardedBy("ImfLock.class")
+ boolean mBoundToAccessibility;
+
+ @GuardedBy("ImfLock.class")
+ @NonNull
+ ImeBindingState mImeBindingState = ImeBindingState.newEmptyState();
+
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ ClientState mCurClient = null;
+
+ @GuardedBy("ImfLock.class")
+ boolean mInFullscreenMode;
+
+ /**
+ * The {@link IRemoteInputConnection} last provided by the current client.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ IRemoteInputConnection mCurInputConnection;
+
+ /**
+ * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to
+ * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ ImeOnBackInvokedDispatcher mCurImeDispatcher;
+
+ /**
+ * The {@link IRemoteAccessibilityInputConnection} last provided by the current client.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection;
+
+ /**
+ * The {@link EditorInfo} last provided by the current client.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ EditorInfo mCurEditorInfo;
+
+ /**
+ * The token tracking the current IME show request that is waiting for a connection to an
+ * IME, otherwise {@code null}.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ ImeTracker.Token mCurStatsToken;
+
+ /**
+ * Currently enabled session.
+ */
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ InputMethodManagerService.SessionState mEnabledSession;
+
+ @GuardedBy("ImfLock.class")
+ @Nullable
+ SparseArray<InputMethodManagerService.AccessibilitySessionState>
+ mEnabledAccessibilitySessions = new SparseArray<>();
+
+ /**
* Intended to be instantiated only from this file.
*/
private UserData(@UserIdInt int userId,
diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
index 757c07c..41aac32 100644
--- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
+++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java
@@ -254,7 +254,7 @@
synchronized (ImfLock.class) {
ClientState cs = imms.getClientStateLocked(client);
if (cs != null) {
- imms.requestClientSessionLocked(cs);
+ imms.requestClientSessionLocked(cs, userId);
imms.requestClientSessionForAccessibilityLocked(cs);
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 7a722bc..a0aad52 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -81,7 +81,6 @@
import java.util.Objects;
import java.util.Optional;
import java.util.PriorityQueue;
-import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledThreadPoolExecutor;
@@ -162,8 +161,8 @@
new PriorityQueue<>(
Comparator.comparingLong(ReliableMessageRecord::getTimestamp));
- // The test mode manager that manages behaviors during test mode.
- private final TestModeManager mTestModeManager = new TestModeManager();
+ // The test mode manager that manages behaviors during test mode
+ private final ContextHubTestModeManager mTestModeManager = new ContextHubTestModeManager();
// The period of the recurring time
private static final int PERIOD_METRIC_QUERY_DAYS = 1;
@@ -226,17 +225,20 @@
@Override
public void handleNanoappMessage(short hostEndpointId, NanoAppMessage message,
List<String> nanoappPermissions, List<String> messagePermissions) {
- if (Flags.reliableMessageImplementation()
+ // Only process the message normally if not using test mode manager or if
+ // the test mode manager call returned false as this indicates it did not
+ // process the message.
+ boolean useTestModeManager = Flags.reliableMessageImplementation()
&& Flags.reliableMessageTestModeBehavior()
- && mIsTestModeEnabled.get()
- && mTestModeManager.handleNanoappMessage(mContextHubId, hostEndpointId,
- message, nanoappPermissions, messagePermissions)) {
- // The TestModeManager handled the nanoapp message, so return here.
- return;
+ && mIsTestModeEnabled.get();
+ if (!useTestModeManager
+ || !mTestModeManager.handleNanoappMessage(() -> {
+ handleClientMessageCallback(mContextHubId, hostEndpointId,
+ message, nanoappPermissions, messagePermissions);
+ }, message)) {
+ handleClientMessageCallback(mContextHubId, hostEndpointId,
+ message, nanoappPermissions, messagePermissions);
}
-
- handleClientMessageCallback(mContextHubId, hostEndpointId, message,
- nanoappPermissions, messagePermissions);
}
@Override
@@ -261,8 +263,6 @@
* Records a reliable message from a nanoapp for duplicate detection.
*/
private static class ReliableMessageRecord {
- public static final int TIMEOUT_NS = 1000000000;
-
public int mContextHubId;
public long mTimestamp;
public int mMessageSequenceNumber;
@@ -297,56 +297,8 @@
}
public boolean isExpired() {
- return mTimestamp + TIMEOUT_NS < SystemClock.elapsedRealtimeNanos();
- }
- }
-
- /**
- * A class to manage behaviors during test mode. This is used for testing.
- */
- private class TestModeManager {
- /**
- * Probability (in percent) of duplicating a message.
- */
- private static final int MESSAGE_DUPLICATION_PROBABILITY_PERCENT = 50;
-
- /**
- * The number of total messages to send when the duplicate event happens.
- */
- private static final int NUM_MESSAGES_TO_DUPLICATE = 3;
-
- /**
- * A probability percent for a certain event.
- */
- private static final int MAX_PROBABILITY_PERCENT = 100;
-
- private final Random mRandom = new Random();
-
- /**
- * @return whether the message was handled
- * @see ContextHubServiceCallback#handleNanoappMessage
- */
- public boolean handleNanoappMessage(int contextHubId,
- short hostEndpointId, NanoAppMessage message,
- List<String> nanoappPermissions, List<String> messagePermissions) {
- if (!message.isReliable()) {
- return false;
- }
-
- if (Flags.reliableMessageDuplicateDetectionService()
- && mRandom.nextInt(MAX_PROBABILITY_PERCENT)
- < MESSAGE_DUPLICATION_PROBABILITY_PERCENT) {
- Log.i(TAG, "[TEST MODE] Duplicating message ("
- + NUM_MESSAGES_TO_DUPLICATE
- + " sends) with message sequence number: "
- + message.getMessageSequenceNumber());
- for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) {
- handleClientMessageCallback(contextHubId, hostEndpointId,
- message, nanoappPermissions, messagePermissions);
- }
- return true;
- }
- return false;
+ return mTimestamp + ContextHubTransactionManager.RELIABLE_MESSAGE_TIMEOUT.toNanos()
+ < SystemClock.elapsedRealtimeNanos();
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java b/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
index 6da7a65..2ec9bdb 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubServiceTransaction.java
@@ -27,53 +27,65 @@
*
* @hide
*/
-/* package */ abstract class ContextHubServiceTransaction {
+abstract class ContextHubServiceTransaction {
private final int mTransactionId;
+
@ContextHubTransaction.Type
private final int mTransactionType;
- /** The ID of the nanoapp this transaction is targeted for, null if not applicable. */
private final Long mNanoAppId;
- /**
- * The host package associated with this transaction.
- */
private final String mPackage;
- /**
- * The message sequence number associated with this transaction, null if not applicable.
- */
private final Integer mMessageSequenceNumber;
- /**
- * true if the transaction has already completed, false otherwise
- */
+ private long mNextRetryTime;
+
+ private long mTimeoutTime;
+
+ /** The number of times the transaction has been started (start function called). */
+ private int mNumCompletedStartCalls;
+
+ private final short mHostEndpointId;
+
private boolean mIsComplete = false;
- /* package */ ContextHubServiceTransaction(int id, int type, String packageName) {
+ ContextHubServiceTransaction(int id, int type, String packageName) {
mTransactionId = id;
mTransactionType = type;
mNanoAppId = null;
mPackage = packageName;
mMessageSequenceNumber = null;
+ mNextRetryTime = Long.MAX_VALUE;
+ mTimeoutTime = Long.MAX_VALUE;
+ mNumCompletedStartCalls = 0;
+ mHostEndpointId = Short.MAX_VALUE;
}
- /* package */ ContextHubServiceTransaction(int id, int type, long nanoAppId,
+ ContextHubServiceTransaction(int id, int type, long nanoAppId,
String packageName) {
mTransactionId = id;
mTransactionType = type;
mNanoAppId = nanoAppId;
mPackage = packageName;
mMessageSequenceNumber = null;
+ mNextRetryTime = Long.MAX_VALUE;
+ mTimeoutTime = Long.MAX_VALUE;
+ mNumCompletedStartCalls = 0;
+ mHostEndpointId = Short.MAX_VALUE;
}
- /* package */ ContextHubServiceTransaction(int id, int type, String packageName,
- int messageSequenceNumber) {
+ ContextHubServiceTransaction(int id, int type, String packageName,
+ int messageSequenceNumber, short hostEndpointId) {
mTransactionId = id;
mTransactionType = type;
mNanoAppId = null;
mPackage = packageName;
mMessageSequenceNumber = messageSequenceNumber;
+ mNextRetryTime = Long.MAX_VALUE;
+ mTimeoutTime = Long.MAX_VALUE;
+ mNumCompletedStartCalls = 0;
+ mHostEndpointId = hostEndpointId;
}
/**
@@ -95,7 +107,7 @@
*
* @param result the result of the transaction
*/
- /* package */ void onTransactionComplete(@ContextHubTransaction.Result int result) {
+ void onTransactionComplete(@ContextHubTransaction.Result int result) {
}
/**
@@ -106,44 +118,51 @@
* @param result the result of the query
* @param nanoAppStateList the list of nanoapps given by the query response
*/
- /* package */ void onQueryResponse(
+ void onQueryResponse(
@ContextHubTransaction.Result int result, List<NanoAppState> nanoAppStateList) {
}
- /**
- * @return the ID of this transaction
- */
- /* package */ int getTransactionId() {
+ int getTransactionId() {
return mTransactionId;
}
- /**
- * @return the type of this transaction
- * @see ContextHubTransaction.Type
- */
@ContextHubTransaction.Type
- /* package */ int getTransactionType() {
+ int getTransactionType() {
return mTransactionType;
}
- /**
- * @return the message sequence number of this transaction
- */
Integer getMessageSequenceNumber() {
return mMessageSequenceNumber;
}
+ long getNextRetryTime() {
+ return mNextRetryTime;
+ }
+
+ long getTimeoutTime() {
+ return mTimeoutTime;
+ }
+
+ int getNumCompletedStartCalls() {
+ return mNumCompletedStartCalls;
+ }
+
+ short getHostEndpointId() {
+ return mHostEndpointId;
+ }
+
/**
* Gets the timeout period as defined in IContexthub.hal
*
* @return the timeout of this transaction in the specified time unit
*/
- /* package */ long getTimeout(TimeUnit unit) {
+ long getTimeout(TimeUnit unit) {
switch (mTransactionType) {
case ContextHubTransaction.TYPE_LOAD_NANOAPP:
return unit.convert(30L, TimeUnit.SECONDS);
case ContextHubTransaction.TYPE_RELIABLE_MESSAGE:
- return unit.convert(1000L, TimeUnit.MILLISECONDS);
+ return unit.convert(ContextHubTransactionManager.RELIABLE_MESSAGE_TIMEOUT.toNanos(),
+ TimeUnit.NANOSECONDS);
case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
@@ -159,14 +178,23 @@
*
* Should only be called as a result of a response from a Context Hub callback
*/
- /* package */ void setComplete() {
+ void setComplete() {
mIsComplete = true;
}
- /**
- * @return true if the transaction has already completed, false otherwise
- */
- /* package */ boolean isComplete() {
+ void setNextRetryTime(long nextRetryTime) {
+ mNextRetryTime = nextRetryTime;
+ }
+
+ void setTimeoutTime(long timeoutTime) {
+ mTimeoutTime = timeoutTime;
+ }
+
+ void setNumCompletedStartCalls(int numCompletedStartCalls) {
+ mNumCompletedStartCalls = numCompletedStartCalls;
+ }
+
+ boolean isComplete() {
return mIsComplete;
}
@@ -187,7 +215,18 @@
out.append(", messageSequenceNumber = ");
out.append(mMessageSequenceNumber);
}
+ if (mTransactionType == ContextHubTransaction.TYPE_RELIABLE_MESSAGE) {
+ out.append(", nextRetryTime = ");
+ out.append(mNextRetryTime);
+ out.append(", timeoutTime = ");
+ out.append(mTimeoutTime);
+ out.append(", numCompletedStartCalls = ");
+ out.append(mNumCompletedStartCalls);
+ out.append(", hostEndpointId = ");
+ out.append(mHostEndpointId);
+ }
out.append(")");
+
return out.toString();
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
new file mode 100644
index 0000000..e50324e
--- /dev/null
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTestModeManager.java
@@ -0,0 +1,82 @@
+/*
+ * 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.server.location.contexthub;
+
+import android.chre.flags.Flags;
+import android.hardware.location.NanoAppMessage;
+import android.util.Log;
+
+import java.util.Random;
+
+/**
+ * A class to manage behaviors during test mode. This is used for testing.
+ * @hide
+ */
+public class ContextHubTestModeManager {
+ private static final String TAG = "ContextHubTestModeManager";
+
+ /** Probability (in percent) of duplicating a message. */
+ private static final int MESSAGE_DROP_PROBABILITY_PERCENT = 20;
+
+ /** Probability (in percent) of duplicating a message. */
+ private static final int MESSAGE_DUPLICATION_PROBABILITY_PERCENT = 20;
+
+ /** The number of total messages to send when the duplicate event happens. */
+ private static final int NUM_MESSAGES_TO_DUPLICATE = 3;
+
+ /** A probability percent for a certain event. */
+ private static final int MAX_PROBABILITY_PERCENT = 100;
+
+ private final Random mRandom = new Random();
+
+ /**
+ * @return whether the message was handled
+ * @see ContextHubServiceCallback#handleNanoappMessage
+ */
+ public boolean handleNanoappMessage(Runnable handleMessage, NanoAppMessage message) {
+ if (Flags.reliableMessageDuplicateDetectionService()
+ && message.isReliable()
+ && mRandom.nextInt(MAX_PROBABILITY_PERCENT)
+ < MESSAGE_DUPLICATION_PROBABILITY_PERCENT) {
+ Log.i(TAG, "[TEST MODE] Duplicating message ("
+ + NUM_MESSAGES_TO_DUPLICATE
+ + " sends) with message sequence number: "
+ + message.getMessageSequenceNumber());
+ for (int i = 0; i < NUM_MESSAGES_TO_DUPLICATE; ++i) {
+ handleMessage.run();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * @return whether the message was handled
+ * @see IContextHubWrapper#sendMessageToContextHub
+ */
+ public boolean sendMessageToContextHub(NanoAppMessage message) {
+ if (Flags.reliableMessageRetrySupportService()
+ && message.isReliable()
+ && mRandom.nextInt(MAX_PROBABILITY_PERCENT)
+ < MESSAGE_DROP_PROBABILITY_PERCENT) {
+ Log.i(TAG, "[TEST MODE] Dropping message with message sequence number: "
+ + message.getMessageSequenceNumber());
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
index ec94e2b..3051379 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubTransactionManager.java
@@ -16,19 +16,26 @@
package com.android.server.location.contexthub;
+import android.chre.flags.Flags;
import android.hardware.location.ContextHubTransaction;
import android.hardware.location.IContextHubTransactionCallback;
import android.hardware.location.NanoAppBinary;
import android.hardware.location.NanoAppMessage;
import android.hardware.location.NanoAppState;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.util.Log;
+import java.time.Duration;
import java.util.ArrayDeque;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
import java.util.Random;
+import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@@ -47,34 +54,30 @@
/* package */ class ContextHubTransactionManager {
private static final String TAG = "ContextHubTransactionManager";
- /*
- * Maximum number of transaction requests that can be pending at a time
- */
+ public static final Duration RELIABLE_MESSAGE_TIMEOUT = Duration.ofSeconds(1);
+
private static final int MAX_PENDING_REQUESTS = 10000;
- /*
- * The proxy to talk to the Context Hub
- */
+ private static final int RELIABLE_MESSAGE_MAX_NUM_RETRY = 3;
+
+ private static final Duration RELIABLE_MESSAGE_RETRY_WAIT_TIME = Duration.ofMillis(250);
+
+ private static final Duration RELIABLE_MESSAGE_MIN_WAIT_TIME = Duration.ofNanos(1000);
+
private final IContextHubWrapper mContextHubProxy;
- /*
- * The manager for all clients for the service.
- */
private final ContextHubClientManager mClientManager;
- /*
- * The nanoapp state manager for the service
- */
private final NanoAppStateManager mNanoAppStateManager;
- /*
- * A queue containing the current transactions
- */
private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
- /*
- * The next available transaction ID
- */
+ private final Map<Integer, ContextHubServiceTransaction> mReliableMessageTransactionMap =
+ new HashMap<>();
+
+ /** A set of host endpoint IDs that have an active pending transaction. */
+ private final Set<Short> mReliableMessageHostEndpointIdActiveSet = new HashSet<>();
+
private final AtomicInteger mNextAvailableId = new AtomicInteger();
/**
@@ -86,10 +89,12 @@
new AtomicInteger(new Random().nextInt(Integer.MAX_VALUE / 2));
/*
- * An executor and the future object for scheduling timeout timers
+ * An executor and the future object for scheduling timeout timers and
+ * for scheduling the processing of reliable message transactions.
*/
- private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1);
+ private final ScheduledThreadPoolExecutor mExecutor = new ScheduledThreadPoolExecutor(1);
private ScheduledFuture<?> mTimeoutFuture = null;
+ private ScheduledFuture<?> mReliableMessageTransactionFuture = null;
/*
* The list of previous transaction records.
@@ -333,7 +338,7 @@
IContextHubTransactionCallback transactionCallback, String packageName) {
return new ContextHubServiceTransaction(mNextAvailableId.getAndIncrement(),
ContextHubTransaction.TYPE_RELIABLE_MESSAGE, packageName,
- mNextAvailableMessageSequenceNumber.getAndIncrement()) {
+ mNextAvailableMessageSequenceNumber.getAndIncrement(), hostEndpointId) {
@Override
/* package */ int onTransact() {
try {
@@ -416,16 +421,23 @@
return;
}
- if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) {
+ if (mTransactionQueue.size() >= MAX_PENDING_REQUESTS
+ || mReliableMessageTransactionMap.size() >= MAX_PENDING_REQUESTS) {
throw new IllegalStateException("Transaction queue is full (capacity = "
+ MAX_PENDING_REQUESTS + ")");
}
- mTransactionQueue.add(transaction);
mTransactionRecordDeque.add(new TransactionRecord(transaction.toString()));
-
- if (mTransactionQueue.size() == 1) {
- startNextTransaction();
+ if (Flags.reliableMessageRetrySupportService()
+ && transaction.getTransactionType()
+ == ContextHubTransaction.TYPE_RELIABLE_MESSAGE) {
+ mReliableMessageTransactionMap.put(transaction.getMessageSequenceNumber(), transaction);
+ mExecutor.execute(() -> processMessageTransactions());
+ } else {
+ mTransactionQueue.add(transaction);
+ if (mTransactionQueue.size() == 1) {
+ startNextTransaction();
+ }
}
}
@@ -455,26 +467,42 @@
/* package */
synchronized void onMessageDeliveryResponse(int messageSequenceNumber, boolean success) {
- ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (!Flags.reliableMessageRetrySupportService()) {
+ ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+ if (transaction == null) {
+ Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+ return;
+ }
+
+ Integer transactionMessageSequenceNumber = transaction.getMessageSequenceNumber();
+ if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE
+ || transactionMessageSequenceNumber == null
+ || transactionMessageSequenceNumber != messageSequenceNumber) {
+ Log.w(TAG, "Received unexpected message transaction response (expected message "
+ + "sequence number = "
+ + transaction.getMessageSequenceNumber()
+ + ", received messageSequenceNumber = " + messageSequenceNumber + ")");
+ return;
+ }
+
+ transaction.onTransactionComplete(success ? ContextHubTransaction.RESULT_SUCCESS :
+ ContextHubTransaction.RESULT_FAILED_AT_HUB);
+ removeTransactionAndStartNext();
+ return;
+ }
+
+ ContextHubServiceTransaction transaction =
+ mReliableMessageTransactionMap.get(messageSequenceNumber);
if (transaction == null) {
- Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+ Log.w(TAG, "Could not find reliable message transaction with message sequence number"
+ + messageSequenceNumber);
return;
}
- Integer transactionMessageSequenceNumber = transaction.getMessageSequenceNumber();
- if (transaction.getTransactionType() != ContextHubTransaction.TYPE_RELIABLE_MESSAGE
- || transactionMessageSequenceNumber == null
- || transactionMessageSequenceNumber != messageSequenceNumber) {
- Log.w(TAG, "Received unexpected message transaction response (expected message "
- + "sequence number = "
- + transaction.getMessageSequenceNumber()
- + ", received messageSequenceNumber = " + messageSequenceNumber + ")");
- return;
- }
-
- transaction.onTransactionComplete(success ? ContextHubTransaction.RESULT_SUCCESS :
- ContextHubTransaction.RESULT_FAILED_AT_HUB);
- removeTransactionAndStartNext();
+ completeMessageTransaction(transaction,
+ success ? ContextHubTransaction.RESULT_SUCCESS
+ : ContextHubTransaction.RESULT_FAILED_AT_HUB);
+ mExecutor.execute(() -> processMessageTransactions());
}
/**
@@ -503,6 +531,15 @@
*/
/* package */
synchronized void onHubReset() {
+ if (Flags.reliableMessageRetrySupportService()) {
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
+ mReliableMessageTransactionMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ completeMessageTransaction(iter.next().getValue(),
+ ContextHubTransaction.RESULT_FAILED_AT_HUB, iter);
+ }
+ }
+
ContextHubServiceTransaction transaction = mTransactionQueue.peek();
if (transaction == null) {
return;
@@ -566,7 +603,7 @@
long timeoutMs = transaction.getTimeout(TimeUnit.MILLISECONDS);
try {
- mTimeoutFuture = mTimeoutExecutor.schedule(
+ mTimeoutFuture = mExecutor.schedule(
onTimeoutFunc, timeoutMs, TimeUnit.MILLISECONDS);
} catch (Exception e) {
Log.e(TAG, "Error when schedule a timer", e);
@@ -579,6 +616,136 @@
}
}
+ /**
+ * Processes message transactions, starting and completing them as needed.
+ * This function is called when adding a message transaction or when a timer
+ * expires for an existing message transaction's retry or timeout. The
+ * internal processing loop will iterate at most twice as if one iteration
+ * completes a transaction, the next iteration can only start new transactions.
+ * If the first iteration does not complete any transaction, the loop will
+ * only iterate once.
+ */
+ private synchronized void processMessageTransactions() {
+ if (!Flags.reliableMessageRetrySupportService()) {
+ return;
+ }
+
+ if (mReliableMessageTransactionFuture != null) {
+ mReliableMessageTransactionFuture.cancel(/* mayInterruptIfRunning= */ false);
+ mReliableMessageTransactionFuture = null;
+ }
+
+ long now = SystemClock.elapsedRealtimeNanos();
+ long nextExecutionTime = Long.MAX_VALUE;
+ boolean continueProcessing;
+ do {
+ continueProcessing = false;
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter =
+ mReliableMessageTransactionMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ ContextHubServiceTransaction transaction = iter.next().getValue();
+ short hostEndpointId = transaction.getHostEndpointId();
+ int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
+ if (numCompletedStartCalls == 0
+ && mReliableMessageHostEndpointIdActiveSet.contains(hostEndpointId)) {
+ continue;
+ }
+
+ long nextRetryTime = transaction.getNextRetryTime();
+ long timeoutTime = transaction.getTimeoutTime();
+ boolean transactionTimedOut = timeoutTime <= now;
+ boolean transactionHitMaxRetries = nextRetryTime <= now
+ && numCompletedStartCalls > RELIABLE_MESSAGE_MAX_NUM_RETRY;
+ if (transactionTimedOut || transactionHitMaxRetries) {
+ completeMessageTransaction(transaction,
+ ContextHubTransaction.RESULT_FAILED_TIMEOUT, iter);
+ continueProcessing = true;
+ } else {
+ if (nextRetryTime <= now || numCompletedStartCalls <= 0) {
+ startMessageTransaction(transaction, now);
+ }
+
+ nextExecutionTime = Math.min(nextExecutionTime,
+ transaction.getNextRetryTime());
+ nextExecutionTime = Math.min(nextExecutionTime,
+ transaction.getTimeoutTime());
+ }
+ }
+ } while (continueProcessing);
+
+ if (nextExecutionTime < Long.MAX_VALUE) {
+ mReliableMessageTransactionFuture = mExecutor.schedule(
+ () -> processMessageTransactions(),
+ Math.max(nextExecutionTime - SystemClock.elapsedRealtimeNanos(),
+ RELIABLE_MESSAGE_MIN_WAIT_TIME.toNanos()),
+ TimeUnit.NANOSECONDS);
+ }
+ }
+
+ /**
+ * Completes a message transaction and removes it from the reliable message map.
+ *
+ * @param transaction The transaction to complete.
+ * @param result The result code.
+ */
+ private void completeMessageTransaction(ContextHubServiceTransaction transaction,
+ @ContextHubTransaction.Result int result) {
+ completeMessageTransaction(transaction, result, /* iter= */ null);
+ }
+
+ /**
+ * Completes a message transaction and removes it from the reliable message map using iter.
+ *
+ * @param transaction The transaction to complete.
+ * @param result The result code.
+ * @param iter The iterator for the reliable message map - used to remove the message directly.
+ */
+ private void completeMessageTransaction(ContextHubServiceTransaction transaction,
+ @ContextHubTransaction.Result int result,
+ Iterator<Map.Entry<Integer, ContextHubServiceTransaction>> iter) {
+ transaction.onTransactionComplete(result);
+
+ if (iter == null) {
+ mReliableMessageTransactionMap.remove(transaction.getMessageSequenceNumber());
+ } else {
+ iter.remove();
+ }
+ mReliableMessageHostEndpointIdActiveSet.remove(transaction.getHostEndpointId());
+
+ Log.d(TAG, "Successfully completed reliable message transaction with "
+ + "message sequence number: " + transaction.getMessageSequenceNumber()
+ + " and result: " + result);
+ }
+
+ /**
+ * Starts a message transaction.
+ *
+ * @param transaction The transaction to start.
+ * @param now The now time.
+ */
+ private void startMessageTransaction(ContextHubServiceTransaction transaction, long now) {
+ int numCompletedStartCalls = transaction.getNumCompletedStartCalls();
+ @ContextHubTransaction.Result int result = transaction.onTransact();
+ if (result == ContextHubTransaction.RESULT_SUCCESS) {
+ Log.d(TAG, "Successfully "
+ + (numCompletedStartCalls == 0 ? "started" : "retried")
+ + " reliable message transaction with message sequence number: "
+ + transaction.getMessageSequenceNumber());
+ } else {
+ Log.w(TAG, "Could not start reliable message transaction with "
+ + "message sequence number: "
+ + transaction.getMessageSequenceNumber()
+ + ", result: " + result);
+ }
+
+ transaction.setNextRetryTime(now + RELIABLE_MESSAGE_RETRY_WAIT_TIME.toNanos());
+ if (transaction.getTimeoutTime() == Long.MAX_VALUE) { // first time starting transaction
+ transaction.setTimeoutTime(now + RELIABLE_MESSAGE_TIMEOUT.toNanos());
+ }
+ transaction.setNumCompletedStartCalls(numCompletedStartCalls + 1);
+ mReliableMessageHostEndpointIdActiveSet.add(transaction.getHostEndpointId());
+ }
+
private int toStatsTransactionResult(@ContextHubTransaction.Result int result) {
switch (result) {
case ContextHubTransaction.RESULT_SUCCESS:
@@ -605,19 +772,34 @@
@Override
public String toString() {
- StringBuilder sb = new StringBuilder(100);
- ContextHubServiceTransaction[] arr;
+ StringBuilder sb = new StringBuilder();
+ int i = 0;
synchronized (this) {
- arr = mTransactionQueue.toArray(new ContextHubServiceTransaction[0]);
- }
- for (int i = 0; i < arr.length; i++) {
- sb.append(i + ": " + arr[i] + "\n");
- }
+ for (ContextHubServiceTransaction transaction: mTransactionQueue) {
+ sb.append(i);
+ sb.append(": ");
+ sb.append(transaction.toString());
+ sb.append("\n");
+ ++i;
+ }
- sb.append("Transaction History:\n");
- Iterator<TransactionRecord> iterator = mTransactionRecordDeque.descendingIterator();
- while (iterator.hasNext()) {
- sb.append(iterator.next() + "\n");
+ if (Flags.reliableMessageRetrySupportService()) {
+ for (ContextHubServiceTransaction transaction:
+ mReliableMessageTransactionMap.values()) {
+ sb.append(i);
+ sb.append(": ");
+ sb.append(transaction.toString());
+ sb.append("\n");
+ ++i;
+ }
+ }
+
+ sb.append("Transaction History:\n");
+ Iterator<TransactionRecord> iterator = mTransactionRecordDeque.descendingIterator();
+ while (iterator.hasNext()) {
+ sb.append(iterator.next());
+ sb.append("\n");
+ }
}
return sb.toString();
}
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 552809b..4fc3d87 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -52,6 +52,7 @@
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* @hide
@@ -432,10 +433,16 @@
// Use this thread in case where the execution requires to be on a service thread.
// For instance, AppOpsManager.noteOp requires the UPDATE_APP_OPS_STATS permission.
- private HandlerThread mHandlerThread =
+ private final HandlerThread mHandlerThread =
new HandlerThread("Context Hub AIDL callback", Process.THREAD_PRIORITY_BACKGROUND);
private Handler mHandler;
+ // True if test mode is enabled for the Context Hub
+ private final AtomicBoolean mIsTestModeEnabled = new AtomicBoolean(false);
+
+ // The test mode manager that manages behaviors during test mode
+ private final ContextHubTestModeManager mTestModeManager = new ContextHubTestModeManager();
+
private class ContextHubAidlCallback extends
android.hardware.contexthub.IContextHubCallback.Stub {
private final int mContextHubId;
@@ -549,6 +556,8 @@
} else {
Log.e(TAG, "mHandleServiceRestartCallback is not set");
}
+
+ mIsTestModeEnabled.set(false);
}
public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
@@ -659,7 +668,17 @@
try {
var msg = ContextHubServiceUtil.createAidlContextHubMessage(
hostEndpointId, message);
- hub.sendMessageToHub(contextHubId, msg);
+
+ // Only process the message normally if not using test mode manager or if
+ // the test mode manager call returned false as this indicates it did not
+ // process the message.
+ boolean useTestModeManager = Flags.reliableMessageImplementation()
+ && Flags.reliableMessageTestModeBehavior()
+ && mIsTestModeEnabled.get();
+ if (!useTestModeManager || !mTestModeManager.sendMessageToContextHub(message)) {
+ hub.sendMessageToHub(contextHubId, msg);
+ }
+
return ContextHubTransaction.RESULT_SUCCESS;
} catch (RemoteException | ServiceSpecificException e) {
return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -828,6 +847,7 @@
return false;
}
+ mIsTestModeEnabled.set(enable);
try {
hub.setTestMode(enable);
return true;
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/power/stats/BatteryStatsImpl.java b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
index eabc979..1b7bf89 100644
--- a/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
+++ b/services/core/java/com/android/server/power/stats/BatteryStatsImpl.java
@@ -28,6 +28,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.usage.NetworkStatsManager;
@@ -1974,13 +1975,15 @@
private WifiManager mWifiManager;
private BluetoothPowerStatsCollector.BluetoothStatsRetriever mBluetoothStatsRetriever;
+ @SuppressLint("WifiManagerPotentialLeak")
void setContext(Context context) {
mPackageManager = context.getPackageManager();
mConsumedEnergyRetriever = new PowerStatsCollector.ConsumedEnergyRetrieverImpl(
LocalServices.getService(PowerStatsInternal.class));
mNetworkStatsManager = context.getSystemService(NetworkStatsManager.class);
- mTelephonyManager = context.getSystemService(TelephonyManager.class);
- mWifiManager = context.getSystemService(WifiManager.class);
+ mTelephonyManager =
+ (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+ mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mBluetoothStatsRetriever = new BluetoothStatsRetrieverImpl(
context.getSystemService(BluetoothManager.class));
}
@@ -11288,10 +11291,11 @@
mCpuPowerStatsCollector.addConsumer(this::recordPowerStats);
mMobileRadioPowerStatsCollector = new MobileRadioPowerStatsCollector(
- mPowerStatsCollectorInjector);
+ mPowerStatsCollectorInjector, this::onMobileRadioPowerStatsRetrieved);
mMobileRadioPowerStatsCollector.addConsumer(this::recordPowerStats);
- mWifiPowerStatsCollector = new WifiPowerStatsCollector(mPowerStatsCollectorInjector);
+ mWifiPowerStatsCollector = new WifiPowerStatsCollector(mPowerStatsCollectorInjector,
+ this::onWifiPowerStatsRetrieved);
mWifiPowerStatsCollector.addConsumer(this::recordPowerStats);
mBluetoothPowerStatsCollector = new BluetoothPowerStatsCollector(
@@ -12320,16 +12324,13 @@
/**
* Distribute WiFi energy info and network traffic to apps.
+ *
* @param info The energy information from the WiFi controller.
*/
@GuardedBy("this")
public void updateWifiState(@Nullable final WifiActivityEnergyInfo info,
final long consumedChargeUC, long elapsedRealtimeMs, long uptimeMs,
@NonNull NetworkStatsManager networkStatsManager) {
- if (mWifiPowerStatsCollector.isEnabled()) {
- return;
- }
-
if (DEBUG_ENERGY) {
synchronized (mWifiNetworkLock) {
Slog.d(TAG, "Updating wifi stats: " + Arrays.toString(mWifiIfaces));
@@ -12347,7 +12348,20 @@
delta = null;
}
}
+ updateWifiBatteryStats(info, delta, consumedChargeUC, elapsedRealtimeMs, uptimeMs);
+ }
+ private void onWifiPowerStatsRetrieved(WifiActivityEnergyInfo wifiActivityEnergyInfo,
+ List<NetworkStatsDelta> networkStatsDeltas, long elapsedRealtimeMs, long uptimeMs) {
+ // Do not populate consumed energy, because energy attribution is done by
+ // WifiPowerStatsProcessor.
+ updateWifiBatteryStats(wifiActivityEnergyInfo, networkStatsDeltas, POWER_DATA_UNAVAILABLE,
+ elapsedRealtimeMs, uptimeMs);
+ }
+
+ private void updateWifiBatteryStats(WifiActivityEnergyInfo info,
+ List<NetworkStatsDelta> delta, long consumedChargeUC, long elapsedRealtimeMs,
+ long uptimeMs) {
synchronized (this) {
if (!mOnBatteryInternal || mIgnoreNextExternalStats) {
if (mIgnoreNextExternalStats) {
@@ -12711,9 +12725,6 @@
: mLastModemActivityInfo.getDelta(activityInfo);
mLastModemActivityInfo = activityInfo;
- // Add modem tx power to history.
- addModemTxPowerToHistory(deltaInfo, elapsedRealtimeMs, uptimeMs);
-
// Grab a separate lock to acquire the network stats, which may do I/O.
List<NetworkStatsDelta> delta = null;
synchronized (mModemNetworkLock) {
@@ -12724,6 +12735,23 @@
}
}
+ updateCellularBatteryStats(deltaInfo, delta, consumedChargeUC, elapsedRealtimeMs, uptimeMs);
+ }
+
+ private void onMobileRadioPowerStatsRetrieved(ModemActivityInfo modemActivityInfo,
+ List<NetworkStatsDelta> networkStatsDeltas, long elapsedRealtimeMs, long uptimeMs) {
+ // Do not populate consumed energy, because energy attribution is done by
+ // MobileRadioPowerStatsProcessor.
+ updateCellularBatteryStats(modemActivityInfo, networkStatsDeltas, POWER_DATA_UNAVAILABLE,
+ elapsedRealtimeMs, uptimeMs);
+ }
+
+ private void updateCellularBatteryStats(@Nullable ModemActivityInfo deltaInfo,
+ @Nullable List<NetworkStatsDelta> delta, long consumedChargeUC, long elapsedRealtimeMs,
+ long uptimeMs) {
+ // Add modem tx power to history.
+ addModemTxPowerToHistory(deltaInfo, elapsedRealtimeMs, uptimeMs);
+
synchronized (this) {
final long totalRadioDurationMs =
mMobileRadioActiveTimer.getTimeSinceMarkLocked(
@@ -13111,7 +13139,6 @@
final long rxTimeMs = deltaInfo.getReceiveTimeMillis(rat, freq);
final int[] txTimesMs = deltaInfo.getTransmitTimeMillis(rat, freq);
-
ratStats.incrementRxDuration(freq, rxTimeMs);
if (isMobileRadioEnergyConsumerSupportedLocked()) {
// Accumulate the power cost of time spent receiving in a particular state.
@@ -15387,16 +15414,15 @@
/*@hide */
public WifiBatteryStats getWifiBatteryStats() {
final int which = STATS_SINCE_CHARGED;
- final long rawRealTimeUs = SystemClock.elapsedRealtime() * 1000;
+ final long rawRealTimeUs = mClock.elapsedRealtime() * 1000;
final ControllerActivityCounter counter = getWifiControllerActivity();
final long idleTimeMs = counter.getIdleTimeCounter().getCountLocked(which);
final long scanTimeMs = counter.getScanTimeCounter().getCountLocked(which);
final long rxTimeMs = counter.getRxTimeCounter().getCountLocked(which);
final long txTimeMs = counter.getTxTimeCounters()[0].getCountLocked(which);
- final long totalControllerActivityTimeMs
- = computeBatteryRealtime(SystemClock.elapsedRealtime() * 1000, which) / 1000;
- final long sleepTimeMs
- = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + txTimeMs);
+ final long totalControllerActivityTimeMs =
+ computeBatteryRealtime(mClock.elapsedRealtime() * 1000, which) / 1000;
+ final long sleepTimeMs = totalControllerActivityTimeMs - (idleTimeMs + rxTimeMs + txTimeMs);
final long energyConsumedMaMs = counter.getPowerCounter().getCountLocked(which);
final long monitoredRailChargeConsumedMaMs =
counter.getMonitoredRailChargeConsumedMaMs().getCountLocked(which);
diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
index 33ea563..c88e1b0 100644
--- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java
@@ -16,6 +16,7 @@
package com.android.server.power.stats;
+import android.annotation.Nullable;
import android.content.pm.PackageManager;
import android.hardware.power.stats.EnergyConsumerType;
import android.net.NetworkStats;
@@ -69,6 +70,13 @@
AccessNetworkConstants.AccessNetworkType.NGRAN
};
+ interface Observer {
+ void onMobileRadioPowerStatsRetrieved(
+ @Nullable ModemActivityInfo modemActivityDelta,
+ @Nullable List<BatteryStatsImpl.NetworkStatsDelta> networkStatsDeltas,
+ long elapsedRealtimeMs, long uptimeMs);
+ }
+
interface Injector {
Handler getHandler();
Clock getClock();
@@ -84,6 +92,7 @@
}
private final Injector mInjector;
+ private final Observer mObserver;
private MobileRadioPowerStatsLayout mLayout;
private boolean mIsInitialized;
@@ -105,13 +114,14 @@
private long mLastCallDuration;
private long mLastScanDuration;
- MobileRadioPowerStatsCollector(Injector injector) {
+ MobileRadioPowerStatsCollector(Injector injector, Observer observer) {
super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
BatteryConsumer.powerComponentIdToString(
BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)),
injector.getUidResolver(),
injector.getClock());
mInjector = injector;
+ mObserver = observer;
}
@Override
@@ -198,10 +208,8 @@
Arrays.fill(mPowerStats.stats, 0);
mPowerStats.uidStats.clear();
- collectModemActivityInfo();
-
- collectNetworkStats();
-
+ ModemActivityInfo modemActivityDelta = collectModemActivityInfo();
+ List<BatteryStatsImpl.NetworkStatsDelta> networkStatsDeltas = collectNetworkStats();
if (mEnergyConsumerIds.length != 0) {
collectEnergyConsumers();
}
@@ -210,12 +218,16 @@
setTimestamp(mClock.elapsedRealtime());
}
+ if (mObserver != null) {
+ mObserver.onMobileRadioPowerStatsRetrieved(modemActivityDelta,
+ networkStatsDeltas, mClock.elapsedRealtime(), mClock.uptimeMillis());
+ }
return mPowerStats;
}
- private void collectModemActivityInfo() {
+ private ModemActivityInfo collectModemActivityInfo() {
if (mTelephonyManager == null) {
- return;
+ return null;
}
CompletableFuture<ModemActivityInfo> immediateFuture = new CompletableFuture<>();
@@ -243,7 +255,7 @@
}
if (activityInfo == null) {
- return;
+ return null;
}
ModemActivityInfo deltaInfo = mLastModemActivityInfo == null
@@ -293,12 +305,13 @@
}
}
}
+ return deltaInfo;
}
- private void collectNetworkStats() {
+ private List<BatteryStatsImpl.NetworkStatsDelta> collectNetworkStats() {
NetworkStats networkStats = mNetworkStatsSupplier.get();
if (networkStats == null) {
- return;
+ return null;
}
List<BatteryStatsImpl.NetworkStatsDelta> delta =
@@ -330,6 +343,7 @@
mLayout.setUidTxPackets(stats, mLayout.getUidTxPackets(stats) + txPackets);
}
}
+ return delta;
}
private void collectEnergyConsumers() {
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
index 4bba649..549a97e 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java
@@ -154,8 +154,11 @@
batteryUsageStatsBuilder.getAggregateBatteryConsumerBuilder(
BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
if (descriptor.powerComponentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID) {
- deviceScope.addConsumedPowerForCustomComponent(descriptor.powerComponentId,
- totalPower[0]);
+ if (batteryUsageStatsBuilder.isSupportedCustomPowerComponent(
+ descriptor.powerComponentId)) {
+ deviceScope.addConsumedPowerForCustomComponent(descriptor.powerComponentId,
+ totalPower[0]);
+ }
} else {
deviceScope.addConsumedPower(descriptor.powerComponentId,
totalPower[0], BatteryConsumer.POWER_MODEL_UNDEFINED);
diff --git a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
index bd04199..6d519ee 100644
--- a/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/WifiPowerStatsCollector.java
@@ -43,6 +43,12 @@
private static final long ENERGY_UNSPECIFIED = -1;
+ interface Observer {
+ void onWifiPowerStatsRetrieved(WifiActivityEnergyInfo info,
+ List<BatteryStatsImpl.NetworkStatsDelta> delta, long elapsedRealtimeMs,
+ long uptimeMs);
+ }
+
interface WifiStatsRetriever {
interface Callback {
void onWifiScanTime(int uid, long scanTimeMs, long batchScanTimeMs);
@@ -66,6 +72,7 @@
}
private final Injector mInjector;
+ private final Observer mObserver;
private WifiPowerStatsLayout mLayout;
private boolean mIsInitialized;
@@ -93,12 +100,13 @@
private final SparseArray<WifiScanTimes> mLastScanTimes = new SparseArray<>();
private long mLastWifiActiveDuration;
- WifiPowerStatsCollector(Injector injector) {
+ WifiPowerStatsCollector(Injector injector, Observer observer) {
super(injector.getHandler(), injector.getPowerStatsCollectionThrottlePeriod(
BatteryConsumer.powerComponentIdToString(
BatteryConsumer.POWER_COMPONENT_WIFI)),
injector.getUidResolver(), injector.getClock());
mInjector = injector;
+ mObserver = observer;
}
@Override
@@ -160,22 +168,27 @@
return null;
}
+ WifiActivityEnergyInfo activityInfo = null;
if (mPowerReportingSupported) {
- collectWifiActivityInfo();
+ activityInfo = collectWifiActivityInfo();
} else {
collectWifiActivityStats();
}
- collectNetworkStats();
+ List<BatteryStatsImpl.NetworkStatsDelta> networkStatsDeltas = collectNetworkStats();
collectWifiScanTime();
if (mEnergyConsumerIds.length != 0) {
collectEnergyConsumers();
}
+ if (mObserver != null) {
+ mObserver.onWifiPowerStatsRetrieved(activityInfo, networkStatsDeltas,
+ mClock.elapsedRealtime(), mClock.uptimeMillis());
+ }
return mPowerStats;
}
- private void collectWifiActivityInfo() {
+ private WifiActivityEnergyInfo collectWifiActivityInfo() {
CompletableFuture<WifiActivityEnergyInfo> immediateFuture = new CompletableFuture<>();
mWifiManager.getWifiActivityEnergyInfoAsync(Runnable::run,
immediateFuture::complete);
@@ -190,7 +203,7 @@
}
if (activityInfo == null) {
- return;
+ return null;
}
long rxDuration = activityInfo.getControllerRxDurationMillis()
@@ -210,6 +223,9 @@
mPowerStats.durationMs = rxDuration + txDuration + scanDuration + idleDuration;
mLastWifiActivityInfo = activityInfo;
+
+ return new WifiActivityEnergyInfo(activityInfo.getTimeSinceBootMillis(),
+ activityInfo.getStackState(), txDuration, rxDuration, scanDuration, idleDuration);
}
private void collectWifiActivityStats() {
@@ -219,12 +235,12 @@
mPowerStats.durationMs = duration;
}
- private void collectNetworkStats() {
+ private List<BatteryStatsImpl.NetworkStatsDelta> collectNetworkStats() {
mPowerStats.uidStats.clear();
NetworkStats networkStats = mNetworkStatsSupplier.get();
if (networkStats == null) {
- return;
+ return null;
}
List<BatteryStatsImpl.NetworkStatsDelta> delta =
@@ -256,6 +272,7 @@
mLayout.setUidTxPackets(stats, mLayout.getUidTxPackets(stats) + txPackets);
}
}
+ return delta;
}
private void collectWifiScanTime() {
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/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index b846947..72c7be3 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -101,6 +101,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.storage.StorageManager;
import android.service.wallpaper.IWallpaperConnection;
import android.service.wallpaper.IWallpaperEngine;
import android.service.wallpaper.IWallpaperService;
@@ -2209,8 +2210,12 @@
public ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId,
IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId,
boolean getCropped) {
- final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL);
- if (!hasPrivilege) checkPermission(MANAGE_EXTERNAL_STORAGE);
+ final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL)
+ || hasPermission(MANAGE_EXTERNAL_STORAGE);
+ if (!hasPrivilege) {
+ mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true,
+ Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId);
+ }
wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), wallpaperUserId, false, true, "getWallpaper", null);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 6b25c84..dce496d 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4578,9 +4578,6 @@
if (!delayed) {
updateReportedVisibilityLocked();
}
-
- // Reset the last saved PiP snap fraction on removal.
- mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent);
mDisplayContent.onRunningActivityChanged();
mRemovingFromDisplay = false;
}
@@ -6744,8 +6741,6 @@
if (task.mLastRecentsAnimationTransaction != null) {
task.clearLastRecentsAnimationTransaction(true /* forceRemoveOverlay */);
}
- // Reset the last saved PiP snap fraction on app stop.
- mDisplayContent.mPinnedTaskController.onActivityHidden(mActivityComponent);
if (isClientVisible()) {
// Though this is usually unlikely to happen, still make sure the client is invisible.
setClientVisible(false);
diff --git a/services/core/java/com/android/server/wm/PinnedTaskController.java b/services/core/java/com/android/server/wm/PinnedTaskController.java
index 4378b4f..755d4c8 100644
--- a/services/core/java/com/android/server/wm/PinnedTaskController.java
+++ b/services/core/java/com/android/server/wm/PinnedTaskController.java
@@ -22,7 +22,6 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import android.app.PictureInPictureParams;
-import android.content.ComponentName;
import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Matrix;
@@ -326,19 +325,6 @@
}
/**
- * Activity is hidden (either stopped or removed), resets the last saved snap fraction
- * so that the default bounds will be returned for the next session.
- */
- void onActivityHidden(ComponentName componentName) {
- if (mPinnedTaskListener == null) return;
- try {
- mPinnedTaskListener.onActivityHidden(componentName);
- } catch (RemoteException e) {
- Slog.e(TAG_WM, "Error delivering reset reentry fraction event.", e);
- }
- }
-
- /**
* Sets the Ime state and height.
*/
void setAdjustedForIme(boolean adjustedForIme, int imeHeight) {
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/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
index d4adba2..9acebf7 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java
@@ -217,8 +217,8 @@
final int displayIdToShowIme = bindingController.getDisplayIdToShowIme();
mInputMethodManagerService.hideCurrentInputLocked(mWindowToken,
statsToken, 0 /* flags */, null /* resultReceiver */,
- HIDE_SWITCH_USER);
- mInputMethodManagerService.onUnbindCurrentMethodByReset();
+ HIDE_SWITCH_USER, mUserId);
+ mInputMethodManagerService.onUnbindCurrentMethodByReset(mUserId);
// Expects applyImeVisibility() -> hideIme() will be called to notify WM for syncing
// the IME hidden state.
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index bb774ee..7b8b712 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1707,7 +1707,8 @@
int initState = Display.STATE_OFF;
mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID);
mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession);
- when(mDisplayOffloadSession.blockScreenOn(any())).thenReturn(true);
+ ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ when(mDisplayOffloadSession.blockScreenOn(argumentCaptor.capture())).thenReturn(true);
// Start with OFF.
when(mHolder.displayPowerState.getScreenState()).thenReturn(initState);
@@ -1721,8 +1722,7 @@
mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
advanceTime(1); // Run updatePowerState
- ArgumentCaptor<Runnable> argumentCaptor = ArgumentCaptor.forClass(Runnable.class);
- verify(mDisplayOffloadSession).blockScreenOn(argumentCaptor.capture());
+ verify(mDisplayOffloadSession).blockScreenOn(any());
// Unblocked
argumentCaptor.getValue().run();
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
index 0275319..ef20946 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsCollectorTest.java
@@ -39,6 +39,7 @@
import android.os.BatteryStats;
import android.os.Handler;
import android.os.OutcomeReceiver;
+import android.os.connectivity.CellularBatteryStats;
import android.platform.test.ravenwood.RavenwoodRule;
import android.telephony.AccessNetworkConstants;
import android.telephony.ActivityStatsTechSpecificInfo;
@@ -167,6 +168,7 @@
public void setup() {
MockitoAnnotations.initMocks(this);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephony);
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)).thenReturn(true);
when(mPowerStatsUidResolver.mapUid(anyInt())).thenAnswer(invocation -> {
int uid = invocation.getArgument(0);
@@ -352,8 +354,48 @@
"UID 42: rx-pkts: 100 rx-B: 1000 tx-pkts: 200 tx-B: 2000");
}
+ @Test
+ public void getCellularBatteryStats() throws Throwable {
+ mBatteryStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO,
+ true);
+
+ mockModemActivityInfo(1000, 2000, 3000, 600, new int[]{100, 200, 300, 400, 500});
+ mockNetworkStats(1100,
+ 5321, 421, 3234, 223,
+ 8000, 80, 4000, 40);
+
+ // This should trigger a baseline sample collection
+ mBatteryStats.onSystemReady(mContext);
+ mStatsRule.waitForBackgroundThread();
+
+ mockModemActivityInfo(20000, 2222, 3333, 666, new int[]{111, 222, 333, 444, 555});
+ mockNetworkStats(21000,
+ 6321, 521, 7234, 423,
+ 8888, 88, 4444, 44);
+
+ mStatsRule.setTime(30000, 30000);
+ mBatteryStats.getPowerStatsCollector(BatteryConsumer.POWER_COMPONENT_MOBILE_RADIO)
+ .schedule();
+ mStatsRule.waitForBackgroundThread();
+
+ CellularBatteryStats stats = mBatteryStats.getCellularBatteryStats();
+ assertThat(stats.getSleepTimeMillis()).isEqualTo(222);
+ assertThat(stats.getIdleTimeMillis()).isEqualTo(333);
+ assertThat(stats.getRxTimeMillis()).isEqualTo(66);
+ assertThat(stats.getTxTimeMillis(ModemActivityInfo.TX_POWER_LEVEL_0)).isEqualTo(11);
+ assertThat(stats.getTxTimeMillis(ModemActivityInfo.TX_POWER_LEVEL_1)).isEqualTo(22);
+ assertThat(stats.getTxTimeMillis(ModemActivityInfo.TX_POWER_LEVEL_2)).isEqualTo(33);
+ assertThat(stats.getTxTimeMillis(ModemActivityInfo.TX_POWER_LEVEL_3)).isEqualTo(44);
+ assertThat(stats.getTxTimeMillis(ModemActivityInfo.TX_POWER_LEVEL_4)).isEqualTo(55);
+ assertThat(stats.getNumPacketsRx()).isEqualTo(934);
+ assertThat(stats.getNumBytesRx()).isEqualTo(19967);
+ assertThat(stats.getNumPacketsTx()).isEqualTo(770);
+ assertThat(stats.getNumBytesTx()).isEqualTo(14214);
+ }
+
private PowerStats collectPowerStats(boolean perNetworkTypeData) throws Throwable {
- MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
+ MobileRadioPowerStatsCollector collector =
+ new MobileRadioPowerStatsCollector(mInjector, null);
collector.setEnabled(true);
when(mConsumedEnergyRetriever.getEnergyConsumerIds(
@@ -462,6 +504,7 @@
.addEntry(new NetworkStats.Entry("mobile", APP_UID3, 0, 0, METERED_NO,
ROAMING_NO, DEFAULT_NETWORK_NO, 314, 281, 314, 281, 111));
}
+ mBatteryStats.setNetworkStats(stats);
when(mNetworkStatsSupplier.get()).thenReturn(stats);
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
index 137c2a6..d7024e5 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MobileRadioPowerStatsProcessorTest.java
@@ -191,7 +191,8 @@
aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
- MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
+ MobileRadioPowerStatsCollector collector =
+ new MobileRadioPowerStatsCollector(mInjector, null);
collector.setEnabled(true);
// Initial empty ModemActivityInfo.
@@ -430,7 +431,8 @@
aggregatedStats.setUidState(APP_UID, STATE_PROCESS_STATE, PROCESS_STATE_FOREGROUND, 0);
aggregatedStats.setUidState(APP_UID2, STATE_PROCESS_STATE, PROCESS_STATE_CACHED, 0);
- MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
+ MobileRadioPowerStatsCollector collector =
+ new MobileRadioPowerStatsCollector(mInjector, null);
collector.setEnabled(true);
// Initial empty ModemActivityInfo.
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
index 548d54c..c268110 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PhoneCallPowerStatsProcessorTest.java
@@ -180,7 +180,8 @@
aggregatedPowerStats.setDeviceState(STATE_POWER, POWER_STATE_OTHER, 0);
aggregatedPowerStats.setDeviceState(STATE_SCREEN, SCREEN_STATE_ON, 0);
- MobileRadioPowerStatsCollector collector = new MobileRadioPowerStatsCollector(mInjector);
+ MobileRadioPowerStatsCollector collector =
+ new MobileRadioPowerStatsCollector(mInjector, null);
collector.setEnabled(true);
// Initial empty ModemActivityInfo.
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
index a280cfe..362607b 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsCollectorTest.java
@@ -40,6 +40,7 @@
import android.os.Handler;
import android.os.WorkSource;
import android.os.connectivity.WifiActivityEnergyInfo;
+import android.os.connectivity.WifiBatteryStats;
import android.platform.test.ravenwood.RavenwoodRule;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
@@ -186,6 +187,7 @@
return uid;
}
});
+ when(mContext.getSystemService(Context.WIFI_SERVICE)).thenReturn(mWifiManager);
mBatteryStats = mStatsRule.getBatteryStats();
}
@@ -319,10 +321,51 @@
+ " scan: 234 batched-scan: 345");
}
+ @Test
+ public void getWifiBatteryStats() throws Throwable {
+ when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(true);
+ mBatteryStats.setPowerStatsCollectorEnabled(BatteryConsumer.POWER_COMPONENT_WIFI,
+ true);
+
+ mockWifiActivityInfo(1000, 600, 100, 2000, 3000);
+ mockNetworkStats(1000);
+ mockNetworkStatsEntry(APP_UID1, 4321, 321, 1234, 23);
+ mockNetworkStatsEntry(APP_UID2, 4000, 40, 2000, 20);
+ mockWifiScanTimes(APP_UID1, 1000, 2000);
+ mockWifiScanTimes(APP_UID2, 3000, 4000);
+
+ // This should trigger a baseline sample collection
+ mBatteryStats.onSystemReady(mContext);
+ mStatsRule.waitForBackgroundThread();
+
+ mockWifiActivityInfo(1100, 6600, 1100, 2200, 3300);
+ mockNetworkStats(1100);
+ mockNetworkStatsEntry(APP_UID1, 5321, 421, 3234, 223);
+ mockNetworkStatsEntry(APP_UID2, 8000, 80, 4000, 40);
+ mockWifiScanTimes(APP_UID1, 1234, 2345);
+ mockWifiScanTimes(APP_UID2, 3100, 4200);
+
+ mStatsRule.setTime(30000, 30000);
+ mBatteryStats.getPowerStatsCollector(BatteryConsumer.POWER_COMPONENT_WIFI)
+ .schedule();
+ mStatsRule.waitForBackgroundThread();
+
+ WifiBatteryStats stats = mBatteryStats.getWifiBatteryStats();
+ assertThat(stats.getNumPacketsRx()).isEqualTo(501);
+ assertThat(stats.getNumBytesRx()).isEqualTo(13321);
+ assertThat(stats.getNumPacketsTx()).isEqualTo(263);
+ assertThat(stats.getNumBytesTx()).isEqualTo(7234);
+ assertThat(stats.getScanTimeMillis()).isEqualTo(2200);
+ assertThat(stats.getRxTimeMillis()).isEqualTo(6000);
+ assertThat(stats.getTxTimeMillis()).isEqualTo(1000);
+ assertThat(stats.getIdleTimeMillis()).isEqualTo(300);
+ assertThat(stats.getSleepTimeMillis()).isEqualTo(30000 - 6000 - 1000 - 300);
+ }
+
private PowerStats collectPowerStats(boolean hasPowerReporting) {
when(mWifiManager.isEnhancedPowerReportingSupported()).thenReturn(hasPowerReporting);
- WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, null);
collector.setEnabled(true);
when(mConsumedEnergyRetriever.getEnergyConsumerIds(EnergyConsumerType.WIFI))
@@ -389,6 +432,7 @@
} else {
mNetworkStats = new NetworkStats(elapsedRealtime, 1);
}
+ mBatteryStats.setNetworkStats(mNetworkStats);
when(mNetworkStatsSupplier.get()).thenReturn(mNetworkStats);
}
@@ -411,6 +455,7 @@
.addEntry(new NetworkStats.Entry("wifi", uid, 0, 0,
METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, rxPackets,
txBytes, txPackets, 100));
+ mBatteryStats.setNetworkStats(mNetworkStats);
reset(mNetworkStatsSupplier);
when(mNetworkStatsSupplier.get()).thenReturn(mNetworkStats);
}
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
index ff56691..7ddaefd 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/WifiPowerStatsProcessorTest.java
@@ -99,6 +99,8 @@
@Mock
private WifiManager mWifiManager;
+ private MockBatteryStatsImpl mBatteryStats;
+
private static class ScanTimes {
public long scanTimeMs;
public long batchScanTimeMs;
@@ -185,6 +187,8 @@
when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI)).thenReturn(true);
when(mPowerStatsUidResolver.mapUid(anyInt()))
.thenAnswer(invocation -> invocation.getArgument(0));
+
+ mBatteryStats = mStatsRule.getBatteryStats();
}
@Test
@@ -200,7 +204,7 @@
PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
- WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, null);
collector.setEnabled(true);
// Initial empty WifiActivityEnergyInfo.
@@ -312,7 +316,7 @@
PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
- WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, null);
collector.setEnabled(true);
// Initial empty WifiActivityEnergyInfo.
@@ -425,7 +429,7 @@
PowerComponentAggregatedPowerStats aggregatedStats = createAggregatedPowerStats(processor);
- WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector);
+ WifiPowerStatsCollector collector = new WifiPowerStatsCollector(mInjector, null);
collector.setEnabled(true);
// Establish a baseline
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java
index 0f3b0aa..636cbee 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java
@@ -37,6 +37,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.media.permission.INativePermissionController;
+import com.android.media.permission.PermissionEnum;
import com.android.media.permission.UidPackageState;
import com.android.server.pm.pkg.PackageState;
@@ -353,6 +354,56 @@
}
@Test
+ public void testSpecialHotwordPermissions() throws Exception {
+ BiPredicate<Integer, String> customPermPred = mock(BiPredicate.class);
+ var initPackageListData =
+ List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two);
+ // expected state
+ // PERM[CAPTURE_AUDIO_HOTWORD]: [10000]
+ // PERM[CAPTURE_AUDIO_OUTPUT]: [10001]
+ // PERM[RECORD_AUDIO]: [10001]
+ // PERM[...]: []
+ when(customPermPred.test(
+ eq(10000), eq(MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_HOTWORD])))
+ .thenReturn(true);
+ when(customPermPred.test(
+ eq(10001), eq(MONITORED_PERMS[PermissionEnum.CAPTURE_AUDIO_OUTPUT])))
+ .thenReturn(true);
+ when(customPermPred.test(eq(10001), eq(MONITORED_PERMS[PermissionEnum.RECORD_AUDIO])))
+ .thenReturn(true);
+ mPermissionProvider =
+ new AudioServerPermissionProvider(
+ initPackageListData, customPermPred, () -> new int[] {0});
+ int HDS_UID = 99001;
+ mPermissionProvider.onServiceStart(mMockPc);
+ clearInvocations(mMockPc);
+ mPermissionProvider.setIsolatedServiceUid(HDS_UID, 10000);
+ verify(mMockPc)
+ .populatePermissionState(
+ eq((byte) PermissionEnum.CAPTURE_AUDIO_HOTWORD),
+ aryEq(new int[] {10000, HDS_UID}));
+ verify(mMockPc)
+ .populatePermissionState(
+ eq((byte) PermissionEnum.CAPTURE_AUDIO_OUTPUT),
+ aryEq(new int[] {10001, HDS_UID}));
+ verify(mMockPc)
+ .populatePermissionState(
+ eq((byte) PermissionEnum.RECORD_AUDIO), aryEq(new int[] {10001, HDS_UID}));
+
+ clearInvocations(mMockPc);
+ mPermissionProvider.clearIsolatedServiceUid(HDS_UID);
+ verify(mMockPc)
+ .populatePermissionState(
+ eq((byte) PermissionEnum.CAPTURE_AUDIO_HOTWORD), aryEq(new int[] {10000}));
+ verify(mMockPc)
+ .populatePermissionState(
+ eq((byte) PermissionEnum.CAPTURE_AUDIO_OUTPUT), aryEq(new int[] {10001}));
+ verify(mMockPc)
+ .populatePermissionState(
+ eq((byte) PermissionEnum.RECORD_AUDIO), aryEq(new int[] {10001}));
+ }
+
+ @Test
public void testPermissionsPopulated_onChange() throws Exception {
var initPackageListData =
List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two);
diff --git a/services/tests/servicestests/src/com/android/server/autofill/RequestIdTest.java b/services/tests/servicestests/src/com/android/server/autofill/RequestIdTest.java
index 6d56c41..60c3659 100644
--- a/services/tests/servicestests/src/com/android/server/autofill/RequestIdTest.java
+++ b/services/tests/servicestests/src/com/android/server/autofill/RequestIdTest.java
@@ -17,17 +17,25 @@
import static com.google.common.truth.Truth.assertThat;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.util.ArrayList;
-import java.util.List;
-
@RunWith(JUnit4.class)
public class RequestIdTest {
+ private static final int TEST_DATASET_SIZE = 300;
+ private static final int TEST_WRAP_SIZE = 50; // Number of request ids before wrap happens
+ private static final String TAG = "RequestIdTest";
+
List<Integer> datasetPrimaryNoWrap = new ArrayList<>();
List<Integer> datasetPrimaryWrap = new ArrayList<>();
List<Integer> datasetSecondaryNoWrap = new ArrayList<>();
@@ -35,151 +43,200 @@
List<Integer> datasetMixedNoWrap = new ArrayList<>();
List<Integer> datasetMixedWrap = new ArrayList<>();
- @Before
- public void setup() throws Exception {
- int datasetSize = 300;
+ List<Integer> manualWrapRequestIdList = Arrays.asList(3, 9, 15,
+ RequestId.MAX_SECONDARY_REQUEST_ID - 5,
+ RequestId.MAX_SECONDARY_REQUEST_ID - 3);
+ List<Integer> manualNoWrapRequestIdList =Arrays.asList(2, 6, 10, 14, 18, 22, 26, 30);
+ List<Integer> manualOneElementRequestIdList = Arrays.asList(1);
+
+ @Before
+ public void setup() throws IllegalArgumentException {
+ Slog.d(TAG, "setup()");
{ // Generate primary only ids that do not wrap
- RequestId requestId = new RequestId(0);
- for (int i = 0; i < datasetSize; i++) {
+ RequestId requestId = new RequestId(RequestId.MIN_PRIMARY_REQUEST_ID);
+ for (int i = 0; i < TEST_DATASET_SIZE; i++) {
datasetPrimaryNoWrap.add(requestId.nextId(false));
}
+ Collections.sort(datasetPrimaryNoWrap);
}
{ // Generate primary only ids that wrap
- RequestId requestId = new RequestId(0xff00);
- for (int i = 0; i < datasetSize; i++) {
+ RequestId requestId = new RequestId(RequestId.MAX_PRIMARY_REQUEST_ID -
+ TEST_WRAP_SIZE * 2);
+ for (int i = 0; i < TEST_DATASET_SIZE; i++) {
datasetPrimaryWrap.add(requestId.nextId(false));
}
+ Collections.sort(datasetPrimaryWrap);
}
{ // Generate SECONDARY only ids that do not wrap
- RequestId requestId = new RequestId(0);
- for (int i = 0; i < datasetSize; i++) {
+ RequestId requestId = new RequestId(RequestId.MIN_SECONDARY_REQUEST_ID);
+ for (int i = 0; i < TEST_DATASET_SIZE; i++) {
datasetSecondaryNoWrap.add(requestId.nextId(true));
}
+ Collections.sort(datasetSecondaryNoWrap);
}
{ // Generate SECONDARY only ids that wrap
- RequestId requestId = new RequestId(0xff00);
- for (int i = 0; i < datasetSize; i++) {
+ RequestId requestId = new RequestId(RequestId.MAX_SECONDARY_REQUEST_ID -
+ TEST_WRAP_SIZE * 2);
+ for (int i = 0; i < TEST_DATASET_SIZE; i++) {
datasetSecondaryWrap.add(requestId.nextId(true));
}
+ Collections.sort(datasetSecondaryWrap);
}
{ // Generate MIXED only ids that do not wrap
- RequestId requestId = new RequestId(0);
- for (int i = 0; i < datasetSize; i++) {
+ RequestId requestId = new RequestId(RequestId.MIN_REQUEST_ID);
+ for (int i = 0; i < TEST_DATASET_SIZE; i++) {
datasetMixedNoWrap.add(requestId.nextId(i % 2 != 0));
}
+ Collections.sort(datasetMixedNoWrap);
}
{ // Generate MIXED only ids that wrap
- RequestId requestId = new RequestId(0xff00);
- for (int i = 0; i < datasetSize; i++) {
+ RequestId requestId = new RequestId(RequestId.MAX_REQUEST_ID -
+ TEST_WRAP_SIZE);
+ for (int i = 0; i < TEST_DATASET_SIZE; i++) {
datasetMixedWrap.add(requestId.nextId(i % 2 != 0));
}
+ Collections.sort(datasetMixedWrap);
}
+ Slog.d(TAG, "finishing setup()");
}
@Test
public void testRequestIdLists() {
+ Slog.d(TAG, "testRequestIdLists()");
for (int id : datasetPrimaryNoWrap) {
assertThat(RequestId.isSecondaryProvider(id)).isFalse();
- assertThat(id >= 0).isTrue();
- assertThat(id < 0xffff).isTrue();
+ assertThat(id).isAtLeast(RequestId.MIN_PRIMARY_REQUEST_ID);
+ assertThat(id).isAtMost(RequestId.MAX_PRIMARY_REQUEST_ID);
}
for (int id : datasetPrimaryWrap) {
assertThat(RequestId.isSecondaryProvider(id)).isFalse();
- assertThat(id >= 0).isTrue();
- assertThat(id < 0xffff).isTrue();
+ assertThat(id).isAtLeast(RequestId.MIN_PRIMARY_REQUEST_ID);
+ assertThat(id).isAtMost(RequestId.MAX_PRIMARY_REQUEST_ID);
}
for (int id : datasetSecondaryNoWrap) {
assertThat(RequestId.isSecondaryProvider(id)).isTrue();
- assertThat(id >= 0).isTrue();
- assertThat(id < 0xffff).isTrue();
+ assertThat(id).isAtLeast(RequestId.MIN_SECONDARY_REQUEST_ID);
+ assertThat(id).isAtMost(RequestId.MAX_SECONDARY_REQUEST_ID);
}
for (int id : datasetSecondaryWrap) {
assertThat(RequestId.isSecondaryProvider(id)).isTrue();
- assertThat(id >= 0).isTrue();
- assertThat(id < 0xffff).isTrue();
+ assertThat(id).isAtLeast(RequestId.MIN_SECONDARY_REQUEST_ID);
+ assertThat(id).isAtMost(RequestId.MAX_SECONDARY_REQUEST_ID);
}
}
@Test
- public void testRequestIdGeneration() {
- RequestId requestId = new RequestId(0);
+ public void testCreateNewRequestId() {
+ Slog.d(TAG, "testCreateNewRequestId()");
+ for (int i = 0; i < 100000; i++) {
+ RequestId requestId = new RequestId();
+ assertThat(requestId.getRequestId()).isAtLeast(RequestId.MIN_REQUEST_ID);
+ assertThat(requestId.getRequestId()).isAtMost(RequestId.MAX_START_ID);
+ }
+ }
+ @Test
+ public void testGetNextRequestId() throws IllegalArgumentException{
+ Slog.d(TAG, "testGetNextRequestId()");
+ RequestId requestId = new RequestId();
// Large Primary
for (int i = 0; i < 100000; i++) {
int y = requestId.nextId(false);
assertThat(RequestId.isSecondaryProvider(y)).isFalse();
- assertThat(y >= 0).isTrue();
- assertThat(y < 0xffff).isTrue();
+ assertThat(y).isAtLeast(RequestId.MIN_PRIMARY_REQUEST_ID);
+ assertThat(y).isAtMost(RequestId.MAX_PRIMARY_REQUEST_ID);
}
// Large Secondary
- requestId = new RequestId(0);
+ requestId = new RequestId();
for (int i = 0; i < 100000; i++) {
int y = requestId.nextId(true);
assertThat(RequestId.isSecondaryProvider(y)).isTrue();
- assertThat(y >= 0).isTrue();
- assertThat(y < 0xffff).isTrue();
+ assertThat(y).isAtLeast(RequestId.MIN_SECONDARY_REQUEST_ID);
+ assertThat(y).isAtMost(RequestId.MAX_SECONDARY_REQUEST_ID);
}
// Large Mixed
- requestId = new RequestId(0);
+ requestId = new RequestId();
for (int i = 0; i < 50000; i++) {
int y = requestId.nextId(i % 2 != 0);
- assertThat(RequestId.isSecondaryProvider(y)).isEqualTo(i % 2 == 0);
- assertThat(y >= 0).isTrue();
- assertThat(y < 0xffff).isTrue();
+ assertThat(y).isAtLeast(RequestId.MIN_REQUEST_ID);
+ assertThat(y).isAtMost(RequestId.MAX_REQUEST_ID);
}
}
@Test
public void testGetLastRequestId() {
- // In this test, request ids are generated FIFO, so the last entry is also the last
- // request
+ Slog.d(TAG, "testGetLastRequestId()");
- { // Primary no wrap
- int lastIdIndex = datasetPrimaryNoWrap.size() - 1;
- int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetPrimaryNoWrap);
- assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
- }
-
- { // Primary wrap
- int lastIdIndex = datasetPrimaryWrap.size() - 1;
- int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetPrimaryWrap);
+ { // Primary no wrap
+ int lastIdIndex = datasetPrimaryNoWrap.size() - 1;
+ int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetPrimaryNoWrap);
assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
}
- { // Secondary no wrap
+ { // Primary wrap
+ // The last index would be the # of request ids left after wrap
+ // minus 1 (index starts at 0)
+ int lastIdIndex = TEST_DATASET_SIZE - TEST_WRAP_SIZE - 1;
+ int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetPrimaryWrap);
+ assertThat(lastComputedIdIndex).isEqualTo(lastIdIndex);
+ }
+
+ { // Secondary no wrap
int lastIdIndex = datasetSecondaryNoWrap.size() - 1;
int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetSecondaryNoWrap);
assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
}
- { // Secondary wrap
- int lastIdIndex = datasetSecondaryWrap.size() - 1;
+ { // Secondary wrap
+ int lastIdIndex = TEST_DATASET_SIZE - TEST_WRAP_SIZE - 1;
int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetSecondaryWrap);
assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
}
- { // Mixed no wrap
+ { // Mixed no wrap
int lastIdIndex = datasetMixedNoWrap.size() - 1;
int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetMixedNoWrap);
assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
}
- { // Mixed wrap
- int lastIdIndex = datasetMixedWrap.size() - 1;
+ { // Mixed wrap
+ int lastIdIndex = TEST_DATASET_SIZE - TEST_WRAP_SIZE - 1;
int lastComputedIdIndex = RequestId.getLastRequestIdIndex(datasetMixedWrap);
assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
}
+ { // Manual wrap
+ int lastIdIndex = 2; // [3, 9, 15,
+ // MAX_SECONDARY_REQUEST_ID - 5, MAX_SECONDARY_REQUEST_ID - 3]
+ int lastComputedIdIndex = RequestId.getLastRequestIdIndex(manualWrapRequestIdList);
+ assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
+ }
+
+ { // Manual no wrap
+ int lastIdIndex = manualNoWrapRequestIdList.size() - 1; // [2, 6, 10, 14,
+ // 18, 22, 26, 30]
+ int lastComputedIdIndex = RequestId.getLastRequestIdIndex(manualNoWrapRequestIdList);
+ assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
+
+ }
+
+ { // Manual one element
+ int lastIdIndex = 0; // [1]
+ int lastComputedIdIndex = RequestId.getLastRequestIdIndex(
+ manualOneElementRequestIdList);
+ assertThat(lastIdIndex).isEqualTo(lastComputedIdIndex);
+
+ }
}
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index 9cd3186..36a5cda 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -393,6 +393,18 @@
testAuthenticate_throwsSecurityException(promptInfo);
}
+ @Test
+ public void testCanAuthenticate_throwsWhenUsingAdvancedApis() {
+ mAuthService = new AuthService(mContext, mInjector);
+ mAuthService.onStart();
+
+ assertThrows(SecurityException.class, () -> {
+ mAuthService.mImpl.canAuthenticate(TEST_OP_PACKAGE_NAME, 1 /* userId */,
+ BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+ waitForIdle();
+ });
+ }
+
private void testAuthenticate_throwsSecurityException(PromptInfo promptInfo) {
mAuthService = new AuthService(mContext, mInjector);
mAuthService.onStart();
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
index 0678140..e078238 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java
@@ -92,8 +92,6 @@
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Mock
- private GenericWindowPolicyController.PipBlockedCallback mPipBlockedCallback;
- @Mock
private VirtualDeviceManager.ActivityListener mActivityListener;
@Mock
private GenericWindowPolicyController.IntentListenerCallback mIntentListenerCallback;
@@ -140,7 +138,6 @@
gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false);
assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isFalse();
- verify(mPipBlockedCallback, timeout(TIMEOUT_MILLIS)).onEnteringPipBlocked(TEST_UID);
}
@Test
@@ -151,7 +148,6 @@
Arrays.asList(WindowConfiguration.WINDOWING_MODE_FULLSCREEN,
WindowConfiguration.WINDOWING_MODE_PINNED)));
assertThat(gwpc.isEnteringPipAllowed(TEST_UID)).isTrue();
- verify(mPipBlockedCallback, after(TIMEOUT_MILLIS).never()).onEnteringPipBlocked(TEST_UID);
}
@Test
@@ -746,7 +742,6 @@
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
/* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
- /* pipBlockedCallback= */ mPipBlockedCallback,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ mSecureWindowCallback,
/* intentListenerCallback= */ mIntentListenerCallback,
@@ -767,7 +762,6 @@
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
/* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
- /* pipBlockedCallback= */ mPipBlockedCallback,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ mSecureWindowCallback,
/* intentListenerCallback= */ mIntentListenerCallback,
@@ -789,7 +783,6 @@
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
/* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
- /* pipBlockedCallback= */ mPipBlockedCallback,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
/* intentListenerCallback= */ mIntentListenerCallback,
@@ -811,7 +804,6 @@
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
/* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
- /* pipBlockedCallback= */ mPipBlockedCallback,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
/* intentListenerCallback= */ mIntentListenerCallback,
@@ -833,7 +825,6 @@
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
/* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
- /* pipBlockedCallback= */ mPipBlockedCallback,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
/* intentListenerCallback= */ mIntentListenerCallback,
@@ -855,7 +846,6 @@
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
/* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
- /* pipBlockedCallback= */ mPipBlockedCallback,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
/* intentListenerCallback= */ mIntentListenerCallback,
@@ -877,7 +867,6 @@
/* crossTaskNavigationExemptions= */ Collections.singleton(blockedComponent),
/* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
- /* pipBlockedCallback= */ mPipBlockedCallback,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
/* intentListenerCallback= */ mIntentListenerCallback,
@@ -899,7 +888,6 @@
/* crossTaskNavigationExemptions= */ Collections.singleton(allowedComponent),
/* permissionDialogComponent= */ null,
/* activityListener= */ mActivityListener,
- /* pipBlockedCallback= */ mPipBlockedCallback,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
/* intentListenerCallback= */ mIntentListenerCallback,
@@ -922,7 +910,6 @@
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
/* permissionDialogComponent= */ permissionComponent,
/* activityListener= */ mActivityListener,
- /* pipBlockedCallback= */ mPipBlockedCallback,
/* activityBlockedCallback= */ mActivityBlockedCallback,
/* secureWindowCallback= */ null,
/* intentListenerCallback= */ mIntentListenerCallback,
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 52f28b9..b946a43 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -87,7 +87,6 @@
/* crossTaskNavigationExemptions= */ new ArraySet<>(),
/* permissionDialogComponent */ null,
/* activityListener= */ null,
- /* pipBlockedCallback= */ null,
/* activityBlockedCallback= */ null,
/* secureWindowCallback= */ null,
/* intentListenerCallback= */ null,
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 27c383c..bf46154 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -113,6 +113,7 @@
import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
+import com.android.server.pm.UserManagerInternal;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import com.android.server.utils.AlarmQueue;
@@ -1063,6 +1064,18 @@
synchronized (mReportedEvents) {
LinkedList<Event> events = mReportedEvents.get(userId);
if (events == null) {
+ // TODO (b/347644400): callers of this API should verify that the userId passed to
+ // this method exists - there is currently a known case where USER_ALL is passed
+ // here and it would be added to the queue, never to be flushed correctly. The logic
+ // below should only remain as a last-resort catch-all fix.
+ final UserManagerInternal umi = LocalServices.getService(UserManagerInternal.class);
+ if (umi == null || (umi != null && !umi.exists(userId))) {
+ // The userId passed is a non-existent user so don't report the event.
+ Slog.wtf(TAG, "Attempted to report event for non-existent user " + userId
+ + " (" + event.mPackage + "/" + event.mClass
+ + " eventType:" + event.mEventType + ")");
+ return;
+ }
events = new LinkedList<>();
mReportedEvents.put(userId, events);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index cfcc04b..89b9850 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -1165,7 +1165,7 @@
LocalServices.getService(PermissionManagerServiceInternal.class)
.setHotwordDetectionServiceProvider(() -> uid);
mIdentity = new HotwordDetectionServiceIdentity(uid, mVoiceInteractionServiceUid);
- addServiceUidForAudioPolicy(uid);
+ addServiceUidForAudioPolicy(uid, mVoiceInteractionServiceUid);
}
}));
}
@@ -1187,23 +1187,17 @@
});
}
- private void addServiceUidForAudioPolicy(int uid) {
- mScheduledExecutorService.execute(() -> {
- AudioManagerInternal audioManager =
- LocalServices.getService(AudioManagerInternal.class);
- if (audioManager != null) {
- audioManager.addAssistantServiceUid(uid);
- }
- });
+ private void addServiceUidForAudioPolicy(int isolatedUid, int owningUid) {
+ AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class);
+ if (audioManager != null) {
+ audioManager.addAssistantServiceUid(isolatedUid, owningUid);
+ }
}
private void removeServiceUidForAudioPolicy(int uid) {
- mScheduledExecutorService.execute(() -> {
- AudioManagerInternal audioManager =
- LocalServices.getService(AudioManagerInternal.class);
- if (audioManager != null) {
- audioManager.removeAssistantServiceUid(uid);
- }
- });
+ AudioManagerInternal audioManager = LocalServices.getService(AudioManagerInternal.class);
+ if (audioManager != null) {
+ audioManager.removeAssistantServiceUid(uid);
+ }
}
}
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());
diff --git a/wifi/java/src/android/net/wifi/WifiMigration.java b/wifi/java/src/android/net/wifi/WifiMigration.java
index 4fabc0b..1a20a12 100644
--- a/wifi/java/src/android/net/wifi/WifiMigration.java
+++ b/wifi/java/src/android/net/wifi/WifiMigration.java
@@ -19,16 +19,23 @@
import static android.os.Environment.getDataMiscCeDirectory;
import static android.os.Environment.getDataMiscDirectory;
+import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
+import android.net.wifi.flags.Flags;
+import android.os.Binder;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.Process;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.provider.Settings;
+import android.security.legacykeystore.ILegacyKeystore;
import android.util.AtomicFile;
+import android.util.Log;
import android.util.SparseArray;
import java.io.File;
@@ -36,7 +43,11 @@
import java.io.InputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Class used to provide one time hooks for existing OEM devices to migrate their config store
@@ -45,6 +56,8 @@
*/
@SystemApi
public final class WifiMigration {
+ private static final String TAG = "WifiMigration";
+
/**
* Directory to read the wifi config store files from under.
*/
@@ -555,4 +568,49 @@
return data;
}
+
+ /**
+ * Migrate any certificates in Legacy Keystore to the newer WifiBlobstore database.
+ *
+ * @hide
+ */
+ @FlaggedApi(Flags.FLAG_LEGACY_KEYSTORE_TO_WIFI_BLOBSTORE_MIGRATION)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static void migrateLegacyKeystoreToWifiBlobstore() {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ ILegacyKeystore legacyKeystore = WifiBlobStore.getLegacyKeystore();
+ String[] legacyAliases = legacyKeystore.list("", Process.WIFI_UID);
+ if (legacyAliases == null || legacyAliases.length == 0) {
+ Log.i(TAG, "No aliases need to be migrated");
+ return;
+ }
+
+ WifiBlobStore wifiBlobStore = WifiBlobStore.getInstance();
+ List<String> blobstoreAliasList = Arrays.asList(wifiBlobStore.list(""));
+ Set<String> blobstoreAliases = new HashSet<>();
+ blobstoreAliases.addAll(blobstoreAliasList);
+
+ for (String legacyAlias : legacyAliases) {
+ // Only migrate if the alias is not already in WifiBlobstore,
+ // since WifiBlobstore should already contain the latest value.
+ if (!blobstoreAliases.contains(legacyAlias)) {
+ byte[] value = legacyKeystore.get(legacyAlias, Process.WIFI_UID);
+ wifiBlobStore.put(legacyAlias, value);
+ }
+ legacyKeystore.remove(legacyAlias, Process.WIFI_UID);
+ }
+ Log.i(TAG, "Successfully migrated aliases from Legacy Keystore");
+ } catch (ServiceSpecificException e) {
+ if (e.errorCode == ILegacyKeystore.ERROR_SYSTEM_ERROR) {
+ Log.i(TAG, "Legacy Keystore service has been deprecated");
+ } else {
+ Log.e(TAG, "Encountered an exception while migrating aliases. " + e);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Encountered an exception while migrating aliases. " + e);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiMigrationTest.java b/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
new file mode 100644
index 0000000..8a5912f
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/WifiMigrationTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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 android.net.wifi;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.validateMockitoUsage;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.withSettings;
+
+import android.security.legacykeystore.ILegacyKeystore;
+
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+
+/**
+ * Unit tests for {@link WifiMigration}.
+ */
+public class WifiMigrationTest {
+ public static final String TEST_ALIAS = "someAliasString";
+ public static final byte[] TEST_VALUE = new byte[]{10, 11, 12};
+
+ @Mock private ILegacyKeystore mLegacyKeystore;
+ @Mock private WifiBlobStore mWifiBlobStore;
+
+ private MockitoSession mSession;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mSession = ExtendedMockito.mockitoSession()
+ .mockStatic(WifiBlobStore.class, withSettings().lenient())
+ .startMocking();
+ when(WifiBlobStore.getLegacyKeystore()).thenReturn(mLegacyKeystore);
+ when(WifiBlobStore.getInstance()).thenReturn(mWifiBlobStore);
+ when(mLegacyKeystore.get(anyString(), anyInt())).thenReturn(TEST_VALUE);
+ }
+
+ @After
+ public void cleanup() {
+ validateMockitoUsage();
+ if (mSession != null) {
+ mSession.finishMocking();
+ }
+ }
+
+ /**
+ * Verify that the Keystore migration method returns immediately if no aliases
+ * are found in Legacy Keystore.
+ */
+ @Test
+ public void testKeystoreMigrationNoLegacyAliases() throws Exception {
+ when(mLegacyKeystore.list(anyString(), anyInt())).thenReturn(new String[0]);
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstore();
+ verify(mLegacyKeystore).list(anyString(), anyInt());
+ verifyNoMoreInteractions(mLegacyKeystore, mWifiBlobStore);
+ }
+
+ /**
+ * Verify that if all aliases in Legacy Keystore are unique to that database,
+ * all aliases are migrated to WifiBlobstore.
+ */
+ @Test
+ public void testKeystoreMigrationUniqueLegacyAliases() throws Exception {
+ String[] legacyAliases = new String[]{TEST_ALIAS + "1", TEST_ALIAS + "2"};
+ String[] blobstoreAliases = new String[0];
+ when(mLegacyKeystore.list(anyString(), anyInt())).thenReturn(legacyAliases);
+ when(mWifiBlobStore.list(anyString())).thenReturn(blobstoreAliases);
+
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstore();
+ verify(mWifiBlobStore, times(legacyAliases.length)).put(anyString(), any(byte[].class));
+ }
+
+ /**
+ * Verify that if some aliases are shared between Legacy Keystore and WifiBlobstore,
+ * only the ones unique to Legacy Keystore are migrated.
+ */
+ @Test
+ public void testKeystoreMigrationDuplicateLegacyAliases() throws Exception {
+ String uniqueLegacyAlias = TEST_ALIAS + "1";
+ String[] blobstoreAliases = new String[]{TEST_ALIAS + "2", TEST_ALIAS + "3"};
+ String[] legacyAliases =
+ new String[]{blobstoreAliases[0], blobstoreAliases[1], uniqueLegacyAlias};
+ when(mLegacyKeystore.list(anyString(), anyInt())).thenReturn(legacyAliases);
+ when(mWifiBlobStore.list(anyString())).thenReturn(blobstoreAliases);
+
+ // Expect that only the unique legacy alias is migrated to the blobstore
+ WifiMigration.migrateLegacyKeystoreToWifiBlobstore();
+ verify(mWifiBlobStore).list(anyString());
+ verify(mWifiBlobStore).put(eq(uniqueLegacyAlias), any(byte[].class));
+ verifyNoMoreInteractions(mWifiBlobStore);
+ }
+}
diff --git a/wifi/wifi.aconfig b/wifi/wifi.aconfig
index c5bc039..c1effe1 100644
--- a/wifi/wifi.aconfig
+++ b/wifi/wifi.aconfig
@@ -17,3 +17,11 @@
description: "Control the API that allows setting / reading the NetworkProviderInfo's battery charging status"
bug: "305067231"
}
+
+flag {
+ name: "legacy_keystore_to_wifi_blobstore_migration"
+ is_exported: true
+ namespace: "wifi"
+ description: "Add API to migrate all values from Legacy Keystore to the new Wifi Blobstore database"
+ bug: "332560152"
+}