Merge "Update the backup and restore to correct namespace" into main
diff --git a/api/Android.bp b/api/Android.bp
index 6a01677..cdc5cd1 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -230,7 +230,7 @@
cmd: "$(location merge_zips) $(out) $(in)",
srcs: [
":api-stubs-docs-non-updatable{.exportable}",
- ":all-modules-public-stubs-source",
+ ":all-modules-public-stubs-source-exportable",
],
visibility: ["//visibility:private"], // Used by make module in //development, mind
}
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index e8fcf4b..1ebe0cd 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -129,7 +129,7 @@
droidstubs {
name: "framework-doc-stubs",
defaults: ["android-non-updatable-doc-stubs-defaults"],
- srcs: [":all-modules-public-stubs-source"],
+ srcs: [":all-modules-public-stubs-source-exportable"],
api_levels_module: "api_versions_public",
aidl: {
include_dirs: [
diff --git a/api/api.go b/api/api.go
index f32bdc3..5ca24de 100644
--- a/api/api.go
+++ b/api/api.go
@@ -429,8 +429,9 @@
func createPublicStubsSourceFilegroup(ctx android.LoadHookContext, modules proptools.Configurable[[]string]) {
props := fgProps{}
- props.Name = proptools.StringPtr("all-modules-public-stubs-source")
- props.Device_common_srcs = createSrcs(modules, "{.public.stubs.source}")
+ props.Name = proptools.StringPtr("all-modules-public-stubs-source-exportable")
+ transformConfigurableArray(modules, "", ".stubs.source")
+ props.Device_common_srcs = createSrcs(modules, "{.exportable}")
props.Visibility = []string{"//frameworks/base"}
ctx.CreateModule(android.FileGroupFactory, &props)
}
diff --git a/cmds/idmap2/libidmap2/ResourceContainer.cpp b/cmds/idmap2/libidmap2/ResourceContainer.cpp
index 3c0e118..57ae354 100644
--- a/cmds/idmap2/libidmap2/ResourceContainer.cpp
+++ b/cmds/idmap2/libidmap2/ResourceContainer.cpp
@@ -17,6 +17,7 @@
#include "idmap2/ResourceContainer.h"
#include <memory>
+#include <mutex>
#include <string>
#include <utility>
#include <vector>
@@ -296,7 +297,7 @@
} // namespace
struct ApkResourceContainer : public TargetResourceContainer, public OverlayResourceContainer {
- static Result<std::unique_ptr<ApkResourceContainer>> FromPath(const std::string& path);
+ static Result<std::unique_ptr<ApkResourceContainer>> FromPath(std::string path);
// inherited from TargetResourceContainer
Result<bool> DefinesOverlayable() const override;
@@ -320,6 +321,7 @@
Result<const ResState*> GetState() const;
ZipAssetsProvider* GetZipAssets() const;
+ mutable std::mutex state_lock_;
mutable std::variant<std::unique_ptr<ZipAssetsProvider>, ResState> state_;
std::string path_;
};
@@ -330,16 +332,17 @@
}
Result<std::unique_ptr<ApkResourceContainer>> ApkResourceContainer::FromPath(
- const std::string& path) {
+ std::string path) {
auto zip_assets = ZipAssetsProvider::Create(path, 0 /* flags */);
if (zip_assets == nullptr) {
return Error("failed to load zip assets");
}
return std::unique_ptr<ApkResourceContainer>(
- new ApkResourceContainer(std::move(zip_assets), path));
+ new ApkResourceContainer(std::move(zip_assets), std::move(path)));
}
Result<const ResState*> ApkResourceContainer::GetState() const {
+ std::lock_guard lock(state_lock_);
if (auto state = std::get_if<ResState>(&state_); state != nullptr) {
return state;
}
@@ -355,6 +358,7 @@
}
ZipAssetsProvider* ApkResourceContainer::GetZipAssets() const {
+ std::lock_guard lock(state_lock_);
if (auto zip = std::get_if<std::unique_ptr<ZipAssetsProvider>>(&state_); zip != nullptr) {
return zip->get();
}
@@ -427,7 +431,7 @@
Result<std::unique_ptr<TargetResourceContainer>> TargetResourceContainer::FromPath(
std::string path) {
- auto result = ApkResourceContainer::FromPath(path);
+ auto result = ApkResourceContainer::FromPath(std::move(path));
if (!result) {
return result.GetError();
}
@@ -438,7 +442,7 @@
std::string path) {
// Load the path as a fabricated overlay if the file magic indicates this is a fabricated overlay.
if (android::IsFabricatedOverlay(path)) {
- auto result = FabricatedOverlayContainer::FromPath(path);
+ auto result = FabricatedOverlayContainer::FromPath(std::move(path));
if (!result) {
return result.GetError();
}
@@ -446,7 +450,7 @@
}
// Fallback to loading the container as an APK.
- auto result = ApkResourceContainer::FromPath(path);
+ auto result = ApkResourceContainer::FromPath(std::move(path));
if (!result) {
return result.GetError();
}
diff --git a/core/api/current.txt b/core/api/current.txt
index f03ef8c..c0b6ab6 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -8857,7 +8857,7 @@
method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
- field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
+ field public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
}
}
@@ -55634,7 +55634,7 @@
method public android.os.Bundle getExtras();
method public CharSequence getHintText();
method public int getInputType();
- method public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
+ method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_ani_label_for_apis") public android.view.accessibility.AccessibilityNodeInfo getLabelFor();
method @Deprecated @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public android.view.accessibility.AccessibilityNodeInfo getLabeledBy();
method @FlaggedApi("android.view.accessibility.support_multiple_labeledby") @NonNull public java.util.List<android.view.accessibility.AccessibilityNodeInfo> getLabeledByList();
method public int getLiveRegion();
@@ -55733,8 +55733,8 @@
method public void setHintText(CharSequence);
method public void setImportantForAccessibility(boolean);
method public void setInputType(int);
- method public void setLabelFor(android.view.View);
- method public void setLabelFor(android.view.View, int);
+ method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_ani_label_for_apis") public void setLabelFor(android.view.View);
+ method @Deprecated @FlaggedApi("android.view.accessibility.deprecate_ani_label_for_apis") public void setLabelFor(android.view.View, int);
method @Deprecated @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void setLabeledBy(android.view.View);
method @Deprecated @FlaggedApi("android.view.accessibility.support_multiple_labeledby") public void setLabeledBy(android.view.View, int);
method public void setLiveRegion(int);
diff --git a/core/java/android/adaptiveauth/OWNERS b/core/java/android/adaptiveauth/OWNERS
index 0218a78..bc8efa9 100644
--- a/core/java/android/adaptiveauth/OWNERS
+++ b/core/java/android/adaptiveauth/OWNERS
@@ -1 +1 @@
-include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file
+include /services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
\ No newline at end of file
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index f432a22..1dc7742 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -32,7 +32,10 @@
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Log;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -92,9 +95,6 @@
* caching on behalf of other processes.
*/
public boolean shouldBypassCache(@NonNull Q query) {
- if(android.multiuser.Flags.propertyInvalidatedCacheBypassMismatchedUids()) {
- return Binder.getCallingUid() != Process.myUid();
- }
return false;
}
};
@@ -392,8 +392,213 @@
}
}
+ /**
+ * An array of hash maps, indexed by calling UID. The class behaves a bit like a hash map
+ * except that it uses the calling UID internally.
+ */
+ private class CacheMap<Query, Result> {
+
+ // Create a new map for a UID, using the parent's configuration for max size.
+ private LinkedHashMap<Query, Result> createMap() {
+ return new LinkedHashMap<Query, Result>(
+ 2 /* start small */,
+ 0.75f /* default load factor */,
+ true /* LRU access order */) {
+ @GuardedBy("mLock")
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ final int size = size();
+ if (size > mHighWaterMark) {
+ mHighWaterMark = size;
+ }
+ if (size > mMaxEntries) {
+ mMissOverflow++;
+ return true;
+ }
+ return false;
+ }
+ };
+ }
+
+ // An array of maps, indexed by UID.
+ private final SparseArray<LinkedHashMap<Query, Result>> mCache = new SparseArray<>();
+
+ // If true, isolate the hash entries by calling UID. If this is false, allow the cache
+ // entries to be combined in a single hash map.
+ private final boolean mIsolated;
+
+ // Collect statistics.
+ private final boolean mStatistics;
+
+ // An array of booleans to indicate if a UID has been involved in a map access. A value
+ // exists for every UID that was ever involved during cache access. This is updated only
+ // if statistics are being collected.
+ private final SparseBooleanArray mUidSeen;
+
+ // A hash map that ignores the UID. This is used in look-aside fashion just for hit/miss
+ // statistics. This is updated only if statistics are being collected.
+ private final ArraySet<Query> mShadowCache;
+
+ // Shadow statistics. Only hits and misses need to be recorded. These are updated only
+ // if statistics are being collected. The "SelfHits" records hits when the UID is the
+ // process uid.
+ private int mShadowHits;
+ private int mShadowMisses;
+ private int mShadowSelfHits;
+
+ // The process UID.
+ private final int mSelfUid;
+
+ // True in test mode. In test mode, the cache uses Binder.getWorkSource() as the UID.
+ private final boolean mTestMode;
+
+ /**
+ * Create a CacheMap. UID isolation is enabled if the input parameter is true and if the
+ * isolation feature is enabled.
+ */
+ CacheMap(boolean isolate, boolean testMode) {
+ mIsolated = Flags.picIsolateCacheByUid() && isolate;
+ mStatistics = Flags.picIsolatedCacheStatistics() && mIsolated;
+ if (mStatistics) {
+ mUidSeen = new SparseBooleanArray();
+ mShadowCache = new ArraySet<>();
+ } else {
+ mUidSeen = null;
+ mShadowCache = null;
+ }
+ mSelfUid = Process.myUid();
+ mTestMode = testMode;
+ }
+
+ // Return the UID for this cache invocation. If uid isolation is disabled, the value of 0
+ // is returned, which effectively places all entries in a single hash map.
+ private int callerUid() {
+ if (!mIsolated) {
+ return 0;
+ } else if (mTestMode) {
+ return Binder.getCallingWorkSourceUid();
+ } else {
+ return Binder.getCallingUid();
+ }
+ }
+
+ /**
+ * Lookup an entry in the cache.
+ */
+ Result get(Query query) {
+ final int uid = callerUid();
+
+ // Shadow statistics
+ if (mStatistics) {
+ if (mShadowCache.contains(query)) {
+ mShadowHits++;
+ if (uid == mSelfUid) {
+ mShadowSelfHits++;
+ }
+ } else {
+ mShadowMisses++;
+ }
+ }
+
+ var map = mCache.get(uid);
+ if (map != null) {
+ return map.get(query);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Remove an entry from the cache.
+ */
+ void remove(Query query) {
+ final int uid = callerUid();
+ if (mStatistics) {
+ mShadowCache.remove(query);
+ }
+
+ var map = mCache.get(uid);
+ if (map != null) {
+ map.remove(query);
+ }
+ }
+
+ /**
+ * Record an entry in the cache.
+ */
+ void put(Query query, Result result) {
+ final int uid = callerUid();
+ if (mStatistics) {
+ mShadowCache.add(query);
+ mUidSeen.put(uid, true);
+ }
+
+ var map = mCache.get(uid);
+ if (map == null) {
+ map = createMap();
+ mCache.put(uid, map);
+ }
+ map.put(query, result);
+ }
+
+ /**
+ * Return the number of entries in the cache.
+ */
+ int size() {
+ int total = 0;
+ for (int i = 0; i < mCache.size(); i++) {
+ var map = mCache.valueAt(i);
+ total += map.size();
+ }
+ return total;
+ }
+
+ /**
+ * Clear the entries in the cache. Update the shadow statistics.
+ */
+ void clear() {
+ if (mStatistics) {
+ mShadowCache.clear();
+ }
+
+ mCache.clear();
+ }
+
+ // Dump basic statistics, if any are collected. Do nothing if statistics are not enabled.
+ void dump(PrintWriter pw) {
+ if (mStatistics) {
+ pw.println(formatSimple(" ShadowHits: %d, ShadowMisses: %d, ShadowSize: %d",
+ mShadowHits, mShadowMisses, mShadowCache.size()));
+ pw.println(formatSimple(" ShadowUids: %d, SelfUid: %d",
+ mUidSeen.size(), mShadowSelfHits));
+ }
+ }
+
+ // Dump detailed statistics
+ void dumpDetailed(PrintWriter pw) {
+ for (int i = 0; i < mCache.size(); i++) {
+ int uid = mCache.keyAt(i);
+ var map = mCache.valueAt(i);
+
+ Set<Map.Entry<Query, Result>> cacheEntries = map.entrySet();
+ if (cacheEntries.size() == 0) {
+ break;
+ }
+
+ pw.println(" Contents:");
+ pw.println(formatSimple(" Uid: %d\n", uid));
+ for (Map.Entry<Query, Result> entry : cacheEntries) {
+ String key = Objects.toString(entry.getKey());
+ String value = Objects.toString(entry.getValue());
+
+ pw.println(formatSimple(" Key: %s\n Value: %s\n", key, value));
+ }
+ }
+ }
+ }
+
@GuardedBy("mLock")
- private final LinkedHashMap<Query, Result> mCache;
+ private final CacheMap<Query, Result> mCache;
/**
* The nonce handler for this cache.
@@ -895,7 +1100,8 @@
* is allowed to be null in the record constructor to facility reuse of Args instances.
* @hide
*/
- public static record Args(@NonNull String mModule, @Nullable String mApi, int mMaxEntries) {
+ public static record Args(@NonNull String mModule, @Nullable String mApi,
+ int mMaxEntries, boolean mIsolateUids, boolean mTestMode) {
// Validation: the module must be one of the known module strings and the maxEntries must
// be positive.
@@ -909,15 +1115,28 @@
// which is not legal, but there is no reasonable default. Clients must call the api
// method to set the field properly.
public Args(@NonNull String module) {
- this(module, /* api */ null, /* maxEntries */ 32);
+ this(module,
+ null, // api
+ 32, // maxEntries
+ true, // isolateUids
+ false // testMode
+ );
}
public Args api(@NonNull String api) {
- return new Args(mModule, api, mMaxEntries);
+ return new Args(mModule, api, mMaxEntries, mIsolateUids, mTestMode);
}
public Args maxEntries(int val) {
- return new Args(mModule, mApi, val);
+ return new Args(mModule, mApi, val, mIsolateUids, mTestMode);
+ }
+
+ public Args isolateUids(boolean val) {
+ return new Args(mModule, mApi, mMaxEntries, val, mTestMode);
+ }
+
+ public Args testMode(boolean val) {
+ return new Args(mModule, mApi, mMaxEntries, mIsolateUids, val);
}
}
@@ -936,7 +1155,7 @@
mCacheName = cacheName;
mNonce = getNonceHandler(mPropertyName);
mMaxEntries = args.mMaxEntries;
- mCache = createMap();
+ mCache = new CacheMap<>(args.mIsolateUids, args.mTestMode);
mComputer = (computer != null) ? computer : new DefaultComputer<>(this);
registerCache();
}
@@ -1006,28 +1225,6 @@
this(new Args(module).maxEntries(maxEntries).api(api), cacheName, computer);
}
- // Create a map. This should be called only from the constructor.
- private LinkedHashMap<Query, Result> createMap() {
- return new LinkedHashMap<Query, Result>(
- 2 /* start small */,
- 0.75f /* default load factor */,
- true /* LRU access order */) {
- @GuardedBy("mLock")
- @Override
- protected boolean removeEldestEntry(Map.Entry eldest) {
- final int size = size();
- if (size > mHighWaterMark) {
- mHighWaterMark = size;
- }
- if (size > mMaxEntries) {
- mMissOverflow++;
- return true;
- }
- return false;
- }
- };
- }
-
/**
* Register the map in the global list. If the cache is disabled globally, disable it
* now. This method is only ever called from the constructor, which means no other thread has
@@ -1778,8 +1975,8 @@
pw.println(formatSimple(" Cache Name: %s", cacheName()));
pw.println(formatSimple(" Property: %s", mPropertyName));
pw.println(formatSimple(
- " Hits: %d, Misses: %d, Skips: %d, Clears: %d",
- mHits, mMisses, getSkipsLocked(), mClears));
+ " Hits: %d, Misses: %d, Skips: %d, Clears: %d, Uids: %d",
+ mHits, mMisses, getSkipsLocked(), mClears, mCache.size()));
// Print all the skip reasons.
pw.format(" Skip-%s: %d", sNonceName[0], mSkips[0]);
@@ -1794,25 +1991,16 @@
pw.println(formatSimple(
" Current Size: %d, Max Size: %d, HW Mark: %d, Overflows: %d",
mCache.size(), mMaxEntries, mHighWaterMark, mMissOverflow));
+ mCache.dump(pw);
pw.println(formatSimple(" Enabled: %s", mDisabled ? "false" : "true"));
- // No specific cache was requested. This is the default, and no details
- // should be dumped.
- if (!detailed) {
- return;
- }
- Set<Map.Entry<Query, Result>> cacheEntries = mCache.entrySet();
- if (cacheEntries.size() == 0) {
- return;
+ // Dump the contents of the cache.
+ if (detailed) {
+ mCache.dumpDetailed(pw);
}
- pw.println(" Contents:");
- for (Map.Entry<Query, Result> entry : cacheEntries) {
- String key = Objects.toString(entry.getKey());
- String value = Objects.toString(entry.getValue());
-
- pw.println(formatSimple(" Key: %s\n Value: %s\n", key, value));
- }
+ // Separator between caches.
+ pw.println("");
}
}
diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig
index fee071b..be24bfa 100644
--- a/core/java/android/app/admin/flags/flags.aconfig
+++ b/core/java/android/app/admin/flags/flags.aconfig
@@ -367,3 +367,10 @@
description: "Allows DPMS to enable or disable SupervisionService based on whether the device is being managed by the supervision role holder."
bug: "376213673"
}
+
+flag {
+ name: "split_create_managed_profile_enabled"
+ namespace: "enterprise"
+ description: "Split up existing create and provision managed profile API."
+ bug: "375382324"
+}
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
index f703026..acad43b 100644
--- a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -20,7 +20,6 @@
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.appsearch.GenericDocument;
import android.os.Bundle;
import android.os.Parcel;
@@ -65,7 +64,7 @@
*
* <p>See {@link #getResultDocument} for more information on extracting the return value.
*/
- public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
+ public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
/**
* Returns the return value of the executed function.
diff --git a/core/java/android/app/performance.aconfig b/core/java/android/app/performance.aconfig
index f51f748..61b53f9 100644
--- a/core/java/android/app/performance.aconfig
+++ b/core/java/android/app/performance.aconfig
@@ -18,3 +18,20 @@
description: "Enforce PropertyInvalidatedCache.setTestMode() protocol"
bug: "360897450"
}
+
+flag {
+ namespace: "system_performance"
+ name: "pic_isolate_cache_by_uid"
+ is_fixed_read_only: true
+ description: "Ensure that different UIDs use different caches"
+ bug: "373752556"
+}
+
+flag {
+ namespace: "system_performance"
+ name: "pic_isolated_cache_statistics"
+ is_fixed_read_only: true
+ description: "Collects statistics for cache UID isolation strategies"
+ bug: "373752556"
+}
+
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index cc57dc0..ff0bb25 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -398,7 +398,6 @@
* Retrieve the raw Intent contained in this Item.
*/
public Intent getIntent() {
- Intent.maybeMarkAsMissingCreatorToken(mIntent);
return mIntent;
}
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index c054b79..6fa5a9b 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -87,7 +87,6 @@
import android.util.Log;
import android.util.proto.ProtoOutputStream;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.XmlUtils;
import com.android.modules.expresslog.Counter;
@@ -109,7 +108,6 @@
import java.util.Objects;
import java.util.Set;
import java.util.TimeZone;
-import java.util.function.Consumer;
/**
* An intent is an abstract description of an operation to be performed. It
@@ -894,20 +892,6 @@
public static void maybeMarkAsMissingCreatorToken(Object object) {
if (object instanceof Intent intent) {
maybeMarkAsMissingCreatorTokenInternal(intent);
- } else if (object instanceof Parcelable[] parcelables) {
- for (Parcelable p : parcelables) {
- if (p instanceof Intent intent) {
- maybeMarkAsMissingCreatorTokenInternal(intent);
- }
- }
- } else if (object instanceof ArrayList parcelables) {
- int N = parcelables.size();
- for (int i = 0; i < N; i++) {
- Object p = parcelables.get(i);
- if (p instanceof Intent intent) {
- maybeMarkAsMissingCreatorTokenInternal(intent);
- }
- }
}
}
@@ -12220,70 +12204,7 @@
// Stores a creator token for an intent embedded as an extra intent in a top level intent,
private IBinder mCreatorToken;
// Stores all extra keys whose values are intents for a top level intent.
- private ArraySet<NestedIntentKey> mNestedIntentKeys;
- }
-
- /**
- * @hide
- */
- public static class NestedIntentKey {
- /** @hide */
- @IntDef(flag = true, prefix = {"NESTED_INTENT_KEY_TYPE"}, value = {
- NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL,
- NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY,
- NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST,
- NESTED_INTENT_KEY_TYPE_CLIP_DATA,
- })
- @Retention(RetentionPolicy.SOURCE)
- private @interface NestedIntentKeyType {
- }
-
- /**
- * This flag indicates the key is for an extra parcel in mExtras.
- */
- private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL = 1 << 0;
-
- /**
- * This flag indicates the key is for an extra parcel array in mExtras and the index is the
- * index of that array.
- */
- private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY = 1 << 1;
-
- /**
- * This flag indicates the key is for an extra parcel list in mExtras and the index is the
- * index of that list.
- */
- private static final int NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST = 1 << 2;
-
- /**
- * This flag indicates the key is for an extra parcel in mClipData.mItems.
- */
- private static final int NESTED_INTENT_KEY_TYPE_CLIP_DATA = 1 << 3;
-
- // type can be a short or even byte. But then probably cannot use @IntDef?? Also not sure
- // if it is necessary.
- private final @NestedIntentKeyType int mType;
- private final String mKey;
- private final int mIndex;
-
- private NestedIntentKey(@NestedIntentKeyType int type, String key, int index) {
- this.mType = type;
- this.mKey = key;
- this.mIndex = index;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- NestedIntentKey that = (NestedIntentKey) o;
- return mType == that.mType && mIndex == that.mIndex && Objects.equals(mKey, that.mKey);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mType, mKey, mIndex);
- }
+ private ArraySet<String> mExtraIntentKeys;
}
private @Nullable CreatorTokenInfo mCreatorTokenInfo;
@@ -12306,9 +12227,8 @@
}
/** @hide */
- @VisibleForTesting
- public Set<NestedIntentKey> getExtraIntentKeys() {
- return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mNestedIntentKeys;
+ public Set<String> getExtraIntentKeys() {
+ return mCreatorTokenInfo == null ? null : mCreatorTokenInfo.mExtraIntentKeys;
}
/** @hide */
@@ -12326,168 +12246,45 @@
* @hide
*/
public void collectExtraIntentKeys() {
- if (preventIntentRedirect()) {
- collectNestedIntentKeysRecur(new ArraySet<>());
- }
- }
+ if (!preventIntentRedirect()) return;
- private void collectNestedIntentKeysRecur(Set<Intent> visited) {
- if (mExtras != null && !mExtras.isParcelled() && !mExtras.isEmpty()) {
+ if (mExtras != null && !mExtras.isEmpty()) {
for (String key : mExtras.keySet()) {
- Object value = mExtras.get(key);
-
- if (value instanceof Intent intent && !visited.contains(intent)) {
- handleNestedIntent(intent, visited, new NestedIntentKey(
- NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL, key, 0));
- } else if (value instanceof Parcelable[] parcelables) {
- handleParcelableArray(parcelables, key, visited);
- } else if (value instanceof ArrayList<?> parcelables) {
- handleParcelableList(parcelables, key, visited);
- }
- }
- }
-
- if (mClipData != null) {
- for (int i = 0; i < mClipData.getItemCount(); i++) {
- Intent intent = mClipData.getItemAt(i).mIntent;
- if (intent != null && !visited.contains(intent)) {
- handleNestedIntent(intent, visited, new NestedIntentKey(
- NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA, null, i));
+ if (mExtras.get(key) instanceof Intent) {
+ if (mCreatorTokenInfo == null) {
+ mCreatorTokenInfo = new CreatorTokenInfo();
+ }
+ if (mCreatorTokenInfo.mExtraIntentKeys == null) {
+ mCreatorTokenInfo.mExtraIntentKeys = new ArraySet<>();
+ }
+ mCreatorTokenInfo.mExtraIntentKeys.add(key);
}
}
}
}
- private void handleNestedIntent(Intent intent, Set<Intent> visited, NestedIntentKey key) {
- visited.add(intent);
- if (mCreatorTokenInfo == null) {
- mCreatorTokenInfo = new CreatorTokenInfo();
- }
- if (mCreatorTokenInfo.mNestedIntentKeys == null) {
- mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>();
- }
- mCreatorTokenInfo.mNestedIntentKeys.add(key);
- intent.collectNestedIntentKeysRecur(visited);
- }
-
- private void handleParcelableArray(Parcelable[] parcelables, String key, Set<Intent> visited) {
- for (int i = 0; i < parcelables.length; i++) {
- if (parcelables[i] instanceof Intent intent && !visited.contains(intent)) {
- handleNestedIntent(intent, visited, new NestedIntentKey(
- NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY, key, i));
- }
- }
- }
-
- private void handleParcelableList(ArrayList<?> parcelables, String key, Set<Intent> visited) {
- for (int i = 0; i < parcelables.size(); i++) {
- if (parcelables.get(i) instanceof Intent intent && !visited.contains(intent)) {
- handleNestedIntent(intent, visited, new NestedIntentKey(
- NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST, key, i));
- }
- }
- }
-
- private static final Consumer<Intent> CHECK_CREATOR_TOKEN_ACTION = intent -> {
- intent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT;
- if (intent.mExtras != null) {
- intent.mExtras.enableTokenVerification();
- }
- };
-
/** @hide */
public void checkCreatorToken() {
- forEachNestedCreatorToken(CHECK_CREATOR_TOKEN_ACTION);
-
- if (mExtras != null) {
- // mark the bundle as intent extras after calls to getParcelable.
- // otherwise, the logic to mark missing token would run before
- // mark trusted creator token present.
- mExtras.enableTokenVerification();
- }
- }
-
- /** @hide */
- public void forEachNestedCreatorToken(Consumer<? super Intent> action) {
- if (mExtras == null && mClipData == null) return;
-
- if (mCreatorTokenInfo != null && mCreatorTokenInfo.mNestedIntentKeys != null) {
- int N = mCreatorTokenInfo.mNestedIntentKeys.size();
- for (int i = 0; i < N; i++) {
- NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i);
- Intent extraIntent = extractIntentFromKey(key);
-
- if (extraIntent != null) {
- action.accept(extraIntent);
- extraIntent.forEachNestedCreatorToken(action);
- } else {
- Log.w(TAG, getLogMessageForKey(key));
+ if (mExtras == null) return;
+ if (mCreatorTokenInfo != null && mCreatorTokenInfo.mExtraIntentKeys != null) {
+ for (String key : mCreatorTokenInfo.mExtraIntentKeys) {
+ try {
+ Intent extraIntent = mExtras.getParcelable(key, Intent.class);
+ if (extraIntent == null) {
+ Log.w(TAG, "The key {" + key
+ + "} does not correspond to an intent in the bundle.");
+ continue;
+ }
+ extraIntent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT;
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to validate creator token. key: " + key + ".", e);
}
}
}
- }
-
- private Intent extractIntentFromKey(NestedIntentKey key) {
- switch (key.mType) {
- case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL:
- return mExtras == null ? null : mExtras.getParcelable(key.mKey, Intent.class);
- case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY:
- if (mExtras == null) return null;
- Intent[] extraIntents = mExtras.getParcelableArray(key.mKey, Intent.class);
- if (extraIntents != null && key.mIndex < extraIntents.length) {
- return extraIntents[key.mIndex];
- }
- break;
- case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST:
- if (mExtras == null) return null;
- ArrayList<Intent> extraIntentsList = mExtras.getParcelableArrayList(key.mKey,
- Intent.class);
- if (extraIntentsList != null && key.mIndex < extraIntentsList.size()) {
- return extraIntentsList.get(key.mIndex);
- }
- break;
- case NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA:
- if (mClipData == null) return null;
- if (key.mIndex < mClipData.getItemCount()) {
- ClipData.Item item = mClipData.getItemAt(key.mIndex);
- if (item != null) {
- return item.mIntent;
- }
- }
- break;
- }
- return null;
- }
-
- private String getLogMessageForKey(NestedIntentKey key) {
- switch (key.mType) {
- case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL:
- return "The key {" + key + "} does not correspond to an intent in the bundle.";
- case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_ARRAY:
- if (mExtras.getParcelableArray(key.mKey, Intent.class) == null) {
- return "The key {" + key
- + "} does not correspond to a Parcelable[] in the bundle.";
- } else {
- return "Parcelable[" + key.mIndex + "] for key {" + key + "} is not an intent.";
- }
- case NestedIntentKey.NESTED_INTENT_KEY_TYPE_EXTRA_PARCEL_LIST:
- if (mExtras.getParcelableArrayList(key.mKey, Intent.class) == null) {
- return "The key {" + key
- + "} does not correspond to an ArrayList<Parcelable> in the bundle.";
- } else {
- return "List.get(" + key.mIndex + ") for key {" + key + "} is not an intent.";
- }
- case NestedIntentKey.NESTED_INTENT_KEY_TYPE_CLIP_DATA:
- if (key.mIndex >= mClipData.getItemCount()) {
- return "Index out of range for clipData items. index: " + key.mIndex
- + ". item counts: " + mClipData.getItemCount();
- } else {
- return "clipData items at index [" + key.mIndex
- + "] is null or does not contain an intent.";
- }
- default:
- return "Unknown key type: " + key.mType;
- }
+ // mark the bundle as intent extras after calls to getParcelable.
+ // otherwise, the logic to mark missing token would run before
+ // mark trusted creator token present.
+ mExtras.setIsIntentExtra();
}
/**
@@ -12560,19 +12357,7 @@
} else {
out.writeInt(1);
out.writeStrongBinder(mCreatorTokenInfo.mCreatorToken);
-
- if (mCreatorTokenInfo.mNestedIntentKeys != null) {
- final int N = mCreatorTokenInfo.mNestedIntentKeys.size();
- out.writeInt(N);
- for (int i = 0; i < N; i++) {
- NestedIntentKey key = mCreatorTokenInfo.mNestedIntentKeys.valueAt(i);
- out.writeInt(key.mType);
- out.writeString8(key.mKey);
- out.writeInt(key.mIndex);
- }
- } else {
- out.writeInt(0);
- }
+ out.writeArraySet(mCreatorTokenInfo.mExtraIntentKeys);
}
}
}
@@ -12637,18 +12422,7 @@
if (in.readInt() != 0) {
mCreatorTokenInfo = new CreatorTokenInfo();
mCreatorTokenInfo.mCreatorToken = in.readStrongBinder();
-
- N = in.readInt();
- if (N > 0) {
- mCreatorTokenInfo.mNestedIntentKeys = new ArraySet<>(N);
- for (int i = 0; i < N; i++) {
- int type = in.readInt();
- String key = in.readString8();
- int index = in.readInt();
- mCreatorTokenInfo.mNestedIntentKeys.append(
- new NestedIntentKey(type, key, index));
- }
- }
+ mCreatorTokenInfo.mExtraIntentKeys = (ArraySet<String>) in.readArraySet(null);
}
}
}
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
index 9d42b67..506a19c 100644
--- a/core/java/android/hardware/input/KeyGestureEvent.java
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -117,6 +117,8 @@
public static final int KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW = 69;
public static final int KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW = 70;
public static final int KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE = 71;
+ public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN = 72;
+ public static final int KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT = 73;
public static final int FLAG_CANCELLED = 1;
@@ -203,6 +205,8 @@
KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW,
KEY_GESTURE_TYPE_MAXIMIZE_FREEFORM_WINDOW,
KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE,
+ KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
+ KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT,
})
@Retention(RetentionPolicy.SOURCE)
public @interface KeyGestureType {
@@ -773,6 +777,10 @@
return "KEY_GESTURE_TYPE_SNAP_RIGHT_FREEFORM_WINDOW";
case KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE:
return "KEY_GESTURE_TYPE_RESTORE_FREEFORM_WINDOW_SIZE";
+ case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN:
+ return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN";
+ case KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT:
+ return "KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT";
default:
return Integer.toHexString(value);
}
diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java
index 99e7d166..c18fb0c 100644
--- a/core/java/android/os/Bundle.java
+++ b/core/java/android/os/Bundle.java
@@ -281,7 +281,7 @@
}
/** {@hide} */
- public void enableTokenVerification() {
+ public void setIsIntentExtra() {
mFlags |= FLAG_VERIFY_TOKENS_PRESENT;
}
diff --git a/core/java/android/security/flags.aconfig b/core/java/android/security/flags.aconfig
index 1d35344..7cb0ffc 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -120,3 +120,10 @@
description: "Feature flag for exposing KeyStore grant APIs"
bug: "351158708"
}
+
+flag {
+ name: "secure_lockdown"
+ namespace: "biometrics"
+ description: "Feature flag for Secure Lockdown feature"
+ bug: "373422357"
+}
\ No newline at end of file
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index bce51f2..1df3b43 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -37,6 +37,7 @@
import android.telephony.emergency.EmergencyNumber;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.NtnSignalStrength;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.IPhoneStateListener;
@@ -1706,6 +1707,11 @@
@NetworkRegistrationInfo.ServiceType int[] availableServices) {
// not supported on the deprecated interface - Use TelephonyCallback instead
}
+
+ public final void onCarrierRoamingNtnSignalStrengthChanged(
+ @NonNull NtnSignalStrength ntnSignalStrength) {
+ // not supported on the deprecated interface - Use TelephonyCallback instead
+ }
}
private void log(String s) {
diff --git a/core/java/android/telephony/TelephonyCallback.java b/core/java/android/telephony/TelephonyCallback.java
index 64a5533..0d1dc46 100644
--- a/core/java/android/telephony/TelephonyCallback.java
+++ b/core/java/android/telephony/TelephonyCallback.java
@@ -30,6 +30,7 @@
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.MediaQualityStatus;
import android.telephony.ims.MediaThreshold;
+import android.telephony.satellite.NtnSignalStrength;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -695,6 +696,15 @@
public static final int EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED = 44;
/**
+ * Event for listening to carrier roaming non-terrestrial network signal strength changes.
+ *
+ * @see CarrierRoamingNtnModeListener
+ *
+ * @hide
+ */
+ public static final int EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED = 45;
+
+ /**
* @hide
*/
@IntDef(prefix = {"EVENT_"}, value = {
@@ -741,7 +751,8 @@
EVENT_SIMULTANEOUS_CELLULAR_CALLING_SUBSCRIPTIONS_CHANGED,
EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED,
EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED,
- EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED
+ EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED,
+ EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED
})
@Retention(RetentionPolicy.SOURCE)
public @interface TelephonyEvent {
@@ -1805,6 +1816,14 @@
*/
default void onCarrierRoamingNtnAvailableServicesChanged(
@NetworkRegistrationInfo.ServiceType List<Integer> availableServices) {}
+
+ /**
+ * Callback invoked when carrier roaming non-terrestrial network signal strength changes.
+ *
+ * @param ntnSignalStrength non-terrestrial network signal strength.
+ */
+ default void onCarrierRoamingNtnSignalStrengthChanged(
+ @NonNull NtnSignalStrength ntnSignalStrength) {}
}
/**
@@ -2270,5 +2289,18 @@
Binder.withCleanCallingIdentity(() -> mExecutor.execute(
() -> listener.onCarrierRoamingNtnAvailableServicesChanged(ServiceList)));
}
+
+ public void onCarrierRoamingNtnSignalStrengthChanged(
+ @NonNull NtnSignalStrength ntnSignalStrength) {
+ if (!Flags.carrierRoamingNbIotNtn()) return;
+
+ CarrierRoamingNtnModeListener listener =
+ (CarrierRoamingNtnModeListener) mTelephonyCallbackWeakRef.get();
+ if (listener == null) return;
+
+ Binder.withCleanCallingIdentity(() -> mExecutor.execute(
+ () -> listener.onCarrierRoamingNtnSignalStrengthChanged(ntnSignalStrength)));
+
+ }
}
}
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 1dab2cf..90b0bb3 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -47,6 +47,7 @@
import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.NtnSignalStrength;
import android.telephony.satellite.SatelliteStateChangeListener;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1137,6 +1138,23 @@
}
/**
+ * Notify external listeners that carrier roaming non-terrestrial network
+ * signal strength changed.
+ * @param subId subscription ID.
+ * @param ntnSignalStrength non-terrestrial network signal strength.
+ * @hide
+ */
+ public final void notifyCarrierRoamingNtnSignalStrengthChanged(int subId,
+ @NonNull NtnSignalStrength ntnSignalStrength) {
+ try {
+ sRegistry.notifyCarrierRoamingNtnSignalStrengthChanged(subId, ntnSignalStrength);
+ } catch (RemoteException ex) {
+ // system server crash
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Processes potential event changes from the provided {@link TelephonyCallback}.
*
* @param telephonyCallback callback for monitoring callback changes to the telephony state.
@@ -1293,6 +1311,7 @@
eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_MODE_CHANGED);
eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_ELIGIBLE_STATE_CHANGED);
eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_AVAILABLE_SERVICES_CHANGED);
+ eventList.add(TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED);
}
return eventList;
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 14652035..0204517 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -3866,8 +3866,14 @@
* Sets the view for which the view represented by this info serves as a
* label for accessibility purposes.
*
+ * @deprecated Use {@link #addLabeledBy(View)} on the labeled node instead,
+ * since {@link #getLabeledByList()} and {@link #getLabeledBy()} on the
+ * labeled node are not automatically populated when this method is used.
+ *
* @param labeled The view for which this info serves as a label.
*/
+ @FlaggedApi(Flags.FLAG_DEPRECATE_ANI_LABEL_FOR_APIS)
+ @Deprecated
public void setLabelFor(View labeled) {
setLabelFor(labeled, AccessibilityNodeProvider.HOST_VIEW_ID);
}
@@ -3888,9 +3894,15 @@
* This class is made immutable before being delivered to an AccessibilityService.
* </p>
*
+ * @deprecated Use {@link #addLabeledBy(View)} on the labeled node instead,
+ * since {@link #getLabeledByList()} and {@link #getLabeledBy()} on the
+ * labeled node are not automatically populated when this method is used.
+ *
* @param root The root whose virtual descendant serves as a label.
* @param virtualDescendantId The id of the virtual descendant.
*/
+ @FlaggedApi(Flags.FLAG_DEPRECATE_ANI_LABEL_FOR_APIS)
+ @Deprecated
public void setLabelFor(View root, int virtualDescendantId) {
enforceNotSealed();
final int rootAccessibilityViewId = (root != null)
@@ -3902,8 +3914,14 @@
* Gets the node info for which the view represented by this info serves as
* a label for accessibility purposes.
*
+ * @deprecated Use {@link #getLabeledByList()} on the labeled node instead,
+ * since calling {@link #addLabeledBy(View)} or {@link #addLabeledBy(View, int)}
+ * on the labeled node do not automatically provide that node from this method.
+ *
* @return The labeled info.
*/
+ @FlaggedApi(Flags.FLAG_DEPRECATE_ANI_LABEL_FOR_APIS)
+ @Deprecated
public AccessibilityNodeInfo getLabelFor() {
enforceSealed();
return getNodeForAccessibilityId(mConnectionId, mWindowId, mLabelForId);
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index 7177ef3..8a006fa 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -92,6 +92,13 @@
flag {
namespace: "accessibility"
+ name: "deprecate_ani_label_for_apis"
+ description: "Controls the deprecation of AccessibilityNodeInfo labelFor apis"
+ bug: "333783827"
+}
+
+flag {
+ namespace: "accessibility"
name: "fix_merged_content_change_event_v2"
description: "Fixes event type and source of content change event merged in ViewRootImpl"
bug: "277305460"
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index aa4927e..edd9d6c 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -158,3 +158,11 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ name: "writing_tools"
+ namespace: "input_method"
+ description: "Writing tools API"
+ bug: "373788889"
+ is_fixed_read_only: true
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index b5c87868..0e85e04 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -27,6 +27,7 @@
import android.telephony.PhysicalChannelConfig;
import android.telephony.PreciseCallState;
import android.telephony.PreciseDataConnectionState;
+import android.telephony.satellite.NtnSignalStrength;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.emergency.EmergencyNumber;
@@ -85,4 +86,5 @@
void onCarrierRoamingNtnModeChanged(in boolean active);
void onCarrierRoamingNtnEligibleStateChanged(in boolean eligible);
void onCarrierRoamingNtnAvailableServicesChanged(in int[] availableServices);
+ void onCarrierRoamingNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 1c76a6c..0f268d5d 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -29,6 +29,7 @@
import android.telephony.PhoneCapability;
import android.telephony.PhysicalChannelConfig;
import android.telephony.PreciseDataConnectionState;
+import android.telephony.satellite.NtnSignalStrength;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.emergency.EmergencyNumber;
@@ -125,8 +126,10 @@
void notifyCarrierRoamingNtnModeChanged(int subId, in boolean active);
void notifyCarrierRoamingNtnEligibleStateChanged(int subId, in boolean eligible);
void notifyCarrierRoamingNtnAvailableServicesChanged(int subId, in int[] availableServices);
+ void notifyCarrierRoamingNtnSignalStrengthChanged(int subId, in NtnSignalStrength ntnSignalStrength);
void addSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg, String featureId);
void removeSatelliteStateChangeListener(ISatelliteStateChangeListener listener, String pkg);
void notifySatelliteStateChanged(boolean isEnabled);
+
}
diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
index 30deb49..fb6937c 100644
--- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java
+++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java
@@ -63,6 +63,7 @@
private final ArrayList<Part> mParts = new ArrayList<>();
+ private final RectF mSegRectF = new RectF();
private final Rect mPointRect = new Rect();
private final RectF mPointRectF = new RectF();
@@ -198,22 +199,42 @@
mState.mSegSegGap, x + segWidth, totalWidth);
final float end = x + segWidth - endOffset;
- // Transparent is not allowed (and also is the default in the data), so use that
- // as a sentinel to be replaced by default
- mStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
- : mState.mStrokeColor);
- mDashedStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
- : mState.mFadedStrokeColor);
-
- // Leave space for the rounded line cap which extends beyond start/end.
- final float capWidth = mStrokePaint.getStrokeWidth() / 2F;
-
- canvas.drawLine(start + capWidth, centerY, end - capWidth, centerY,
- segment.mDashed ? mDashedStrokePaint : mStrokePaint);
-
// Advance the current position to account for the segment's fraction of the total
// width (ignoring offset and padding)
x += segWidth;
+
+ // No space left to draw the segment
+ if (start > end) continue;
+
+ if (segment.mDashed) {
+ // No caps when the segment is dashed.
+
+ mDashedStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
+ : mState.mFadedStrokeColor);
+ canvas.drawLine(start, centerY, end, centerY, mDashedStrokePaint);
+ } else if (end - start < mState.mStrokeWidth) {
+ // Not enough segment length to draw the caps
+
+ final float rad = (end - start) / 2F;
+ final float capWidth = mStrokePaint.getStrokeWidth() / 2F;
+
+ mFillPaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
+ : mState.mStrokeColor);
+
+ mSegRectF.set(start, centerY - capWidth, end, centerY + capWidth);
+ canvas.drawRoundRect(mSegRectF, rad, rad, mFillPaint);
+ } else {
+ // Leave space for the rounded line cap which extends beyond start/end.
+ final float capWidth = mStrokePaint.getStrokeWidth() / 2F;
+
+ // Transparent is not allowed (and also is the default in the data), so use that
+ // as a sentinel to be replaced by default
+ mStrokePaint.setColor(segment.mColor != Color.TRANSPARENT ? segment.mColor
+ : mState.mStrokeColor);
+
+ canvas.drawLine(start + capWidth, centerY, end - capWidth, centerY,
+ mStrokePaint);
+ }
} else if (part instanceof Point point) {
final float pointWidth = 2 * pointRadius;
float start = x - pointRadius;
@@ -232,7 +253,7 @@
} else {
// TODO: b/367804171 - actually use a vector asset for the default point
// rather than drawing it as a box?
- mPointRectF.set(mPointRect);
+ mPointRectF.set(start, centerY - pointRadius, end, centerY + pointRadius);
final float inset = mState.mPointRectInset;
final float cornerRadius = mState.mPointRectCornerRadius;
mPointRectF.inset(inset, inset);
diff --git a/core/jni/android_hardware_input_InputWindowHandle.cpp b/core/jni/android_hardware_input_InputWindowHandle.cpp
index 69f6334..f1c4913 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.cpp
+++ b/core/jni/android_hardware_input_InputWindowHandle.cpp
@@ -83,68 +83,53 @@
jmethodID ctor;
} gRegionClassInfo;
-static Mutex gHandleMutex;
+// --- Global functions ---
+sp<gui::WindowInfoHandle> android_view_InputWindowHandle_getHandle(JNIEnv* env, jobject obj) {
+ sp<gui::WindowInfoHandle> handle = [&]() {
+ jlong cachedHandle = env->GetLongField(obj, gInputWindowHandleClassInfo.ptr);
+ if (cachedHandle) {
+ return sp<gui::WindowInfoHandle>::fromExisting(
+ reinterpret_cast<gui::WindowInfoHandle*>(cachedHandle));
+ }
-// --- NativeInputWindowHandle ---
+ auto newHandle = sp<gui::WindowInfoHandle>::make();
+ newHandle->incStrong((void*)android_view_InputWindowHandle_getHandle);
+ env->SetLongField(obj, gInputWindowHandleClassInfo.ptr,
+ reinterpret_cast<jlong>(newHandle.get()));
+ return newHandle;
+ }();
-NativeInputWindowHandle::NativeInputWindowHandle(jweak objWeak) :
- mObjWeak(objWeak) {
-}
+ gui::WindowInfo* windowInfo = handle->editInfo();
-NativeInputWindowHandle::~NativeInputWindowHandle() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- env->DeleteWeakGlobalRef(mObjWeak);
-
- // Clear the weak reference to the layer handle and flush any binder ref count operations so we
- // do not hold on to any binder references.
- // TODO(b/139697085) remove this after it can be flushed automatically
- mInfo.touchableRegionCropHandle.clear();
- IPCThreadState::self()->flushCommands();
-}
-
-jobject NativeInputWindowHandle::getInputWindowHandleObjLocalRef(JNIEnv* env) {
- return env->NewLocalRef(mObjWeak);
-}
-
-bool NativeInputWindowHandle::updateInfo() {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
- jobject obj = env->NewLocalRef(mObjWeak);
- if (!obj) {
- releaseChannel();
- return false;
- }
-
- mInfo.touchableRegion.clear();
+ windowInfo->touchableRegion.clear();
jobject tokenObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.token);
if (tokenObj) {
- mInfo.token = ibinderForJavaObject(env, tokenObj);
+ windowInfo->token = ibinderForJavaObject(env, tokenObj);
env->DeleteLocalRef(tokenObj);
} else {
- mInfo.token.clear();
+ windowInfo->token.clear();
}
- mInfo.name = getStringField(env, obj, gInputWindowHandleClassInfo.name, "<null>");
+ windowInfo->name = getStringField(env, obj, gInputWindowHandleClassInfo.name, "<null>");
- mInfo.dispatchingTimeout = std::chrono::milliseconds(
+ windowInfo->dispatchingTimeout = std::chrono::milliseconds(
env->GetLongField(obj, gInputWindowHandleClassInfo.dispatchingTimeoutMillis));
ScopedLocalRef<jobject> frameObj(env,
env->GetObjectField(obj, gInputWindowHandleClassInfo.frame));
- mInfo.frame = JNICommon::rectFromObj(env, frameObj.get());
+ windowInfo->frame = JNICommon::rectFromObj(env, frameObj.get());
- mInfo.surfaceInset = env->GetIntField(obj,
- gInputWindowHandleClassInfo.surfaceInset);
- mInfo.globalScaleFactor = env->GetFloatField(obj,
- gInputWindowHandleClassInfo.scaleFactor);
+ windowInfo->surfaceInset = env->GetIntField(obj, gInputWindowHandleClassInfo.surfaceInset);
+ windowInfo->globalScaleFactor =
+ env->GetFloatField(obj, gInputWindowHandleClassInfo.scaleFactor);
- jobject regionObj = env->GetObjectField(obj,
- gInputWindowHandleClassInfo.touchableRegion);
+ jobject regionObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.touchableRegion);
if (regionObj) {
for (graphics::RegionIterator it(env, regionObj); !it.isDone(); it.next()) {
ARect rect = it.getRect();
- mInfo.addTouchableRegion(Rect(rect.left, rect.top, rect.right, rect.bottom));
+ windowInfo->addTouchableRegion(Rect(rect.left, rect.top, rect.right, rect.bottom));
}
env->DeleteLocalRef(regionObj);
}
@@ -153,49 +138,55 @@
env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsFlags));
const auto type = static_cast<WindowInfo::Type>(
env->GetIntField(obj, gInputWindowHandleClassInfo.layoutParamsType));
- mInfo.layoutParamsFlags = flags;
- mInfo.layoutParamsType = type;
+ windowInfo->layoutParamsFlags = flags;
+ windowInfo->layoutParamsType = type;
- mInfo.inputConfig = static_cast<gui::WindowInfo::InputConfig>(
+ windowInfo->inputConfig = static_cast<gui::WindowInfo::InputConfig>(
env->GetIntField(obj, gInputWindowHandleClassInfo.inputConfig));
- mInfo.touchOcclusionMode = static_cast<TouchOcclusionMode>(
+ windowInfo->touchOcclusionMode = static_cast<TouchOcclusionMode>(
env->GetIntField(obj, gInputWindowHandleClassInfo.touchOcclusionMode));
- mInfo.ownerPid = gui::Pid{env->GetIntField(obj, gInputWindowHandleClassInfo.ownerPid)};
- mInfo.ownerUid = gui::Uid{
+ windowInfo->ownerPid = gui::Pid{env->GetIntField(obj, gInputWindowHandleClassInfo.ownerPid)};
+ windowInfo->ownerUid = gui::Uid{
static_cast<uid_t>(env->GetIntField(obj, gInputWindowHandleClassInfo.ownerUid))};
- mInfo.packageName = getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
- mInfo.displayId =
+ windowInfo->packageName =
+ getStringField(env, obj, gInputWindowHandleClassInfo.packageName, "<null>");
+ windowInfo->displayId =
ui::LogicalDisplayId{env->GetIntField(obj, gInputWindowHandleClassInfo.displayId)};
- jobject inputApplicationHandleObj = env->GetObjectField(obj,
- gInputWindowHandleClassInfo.inputApplicationHandle);
+ jobject inputApplicationHandleObj =
+ env->GetObjectField(obj, gInputWindowHandleClassInfo.inputApplicationHandle);
if (inputApplicationHandleObj) {
std::shared_ptr<InputApplicationHandle> inputApplicationHandle =
android_view_InputApplicationHandle_getHandle(env, inputApplicationHandleObj);
if (inputApplicationHandle != nullptr) {
inputApplicationHandle->updateInfo();
- mInfo.applicationInfo = *(inputApplicationHandle->getInfo());
+ windowInfo->applicationInfo = *(inputApplicationHandle->getInfo());
}
env->DeleteLocalRef(inputApplicationHandleObj);
}
- mInfo.replaceTouchableRegionWithCrop = env->GetBooleanField(obj,
- gInputWindowHandleClassInfo.replaceTouchableRegionWithCrop);
+ windowInfo->replaceTouchableRegionWithCrop =
+ env->GetBooleanField(obj, gInputWindowHandleClassInfo.replaceTouchableRegionWithCrop);
- jobject weakSurfaceCtrl = env->GetObjectField(obj,
- gInputWindowHandleClassInfo.touchableRegionSurfaceControl.ctrl);
+ jobject weakSurfaceCtrl =
+ env->GetObjectField(obj,
+ gInputWindowHandleClassInfo.touchableRegionSurfaceControl.ctrl);
bool touchableRegionCropHandleSet = false;
if (weakSurfaceCtrl) {
// Promote java weak reference.
- jobject strongSurfaceCtrl = env->CallObjectMethod(weakSurfaceCtrl,
- gInputWindowHandleClassInfo.touchableRegionSurfaceControl.get);
+ jobject strongSurfaceCtrl =
+ env->CallObjectMethod(weakSurfaceCtrl,
+ gInputWindowHandleClassInfo.touchableRegionSurfaceControl
+ .get);
if (strongSurfaceCtrl) {
- jlong mNativeObject = env->GetLongField(strongSurfaceCtrl,
- gInputWindowHandleClassInfo.touchableRegionSurfaceControl.mNativeObject);
+ jlong mNativeObject =
+ env->GetLongField(strongSurfaceCtrl,
+ gInputWindowHandleClassInfo.touchableRegionSurfaceControl
+ .mNativeObject);
if (mNativeObject) {
auto ctrl = reinterpret_cast<SurfaceControl *>(mNativeObject);
- mInfo.touchableRegionCropHandle = ctrl->getHandle();
+ windowInfo->touchableRegionCropHandle = ctrl->getHandle();
touchableRegionCropHandleSet = true;
}
env->DeleteLocalRef(strongSurfaceCtrl);
@@ -203,15 +194,15 @@
env->DeleteLocalRef(weakSurfaceCtrl);
}
if (!touchableRegionCropHandleSet) {
- mInfo.touchableRegionCropHandle.clear();
+ windowInfo->touchableRegionCropHandle.clear();
}
jobject windowTokenObj = env->GetObjectField(obj, gInputWindowHandleClassInfo.windowToken);
if (windowTokenObj) {
- mInfo.windowToken = ibinderForJavaObject(env, windowTokenObj);
+ windowInfo->windowToken = ibinderForJavaObject(env, windowTokenObj);
env->DeleteLocalRef(windowTokenObj);
} else {
- mInfo.windowToken.clear();
+ windowInfo->windowToken.clear();
}
ScopedLocalRef<jobject>
@@ -220,41 +211,16 @@
gInputWindowHandleClassInfo
.focusTransferTarget));
if (focusTransferTargetObj.get()) {
- mInfo.focusTransferTarget = ibinderForJavaObject(env, focusTransferTargetObj.get());
+ windowInfo->focusTransferTarget = ibinderForJavaObject(env, focusTransferTargetObj.get());
} else {
- mInfo.focusTransferTarget.clear();
+ windowInfo->focusTransferTarget.clear();
}
- env->DeleteLocalRef(obj);
- return true;
-}
-
-
-// --- Global functions ---
-
-sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
- JNIEnv* env, jobject inputWindowHandleObj) {
- if (!inputWindowHandleObj) {
- return NULL;
- }
-
- AutoMutex _l(gHandleMutex);
-
- jlong ptr = env->GetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr);
- NativeInputWindowHandle* handle;
- if (ptr) {
- handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
- } else {
- jweak objWeak = env->NewWeakGlobalRef(inputWindowHandleObj);
- handle = new NativeInputWindowHandle(objWeak);
- handle->incStrong((void*)android_view_InputWindowHandle_getHandle);
- env->SetLongField(inputWindowHandleObj, gInputWindowHandleClassInfo.ptr,
- reinterpret_cast<jlong>(handle));
- }
return handle;
}
-jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env, gui::WindowInfo windowInfo) {
+jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env,
+ const gui::WindowInfo& windowInfo) {
ScopedLocalRef<jobject>
applicationHandle(env,
android_view_InputApplicationHandle_fromInputApplicationInfo(
@@ -337,18 +303,15 @@
// --- JNI ---
static void android_view_InputWindowHandle_nativeDispose(JNIEnv* env, jobject obj) {
- AutoMutex _l(gHandleMutex);
-
jlong ptr = env->GetLongField(obj, gInputWindowHandleClassInfo.ptr);
- if (ptr) {
- env->SetLongField(obj, gInputWindowHandleClassInfo.ptr, 0);
-
- NativeInputWindowHandle* handle = reinterpret_cast<NativeInputWindowHandle*>(ptr);
- handle->decStrong((void*)android_view_InputWindowHandle_getHandle);
+ if (!ptr) {
+ return;
}
+ env->SetLongField(obj, gInputWindowHandleClassInfo.ptr, 0);
+ auto handle = reinterpret_cast<gui::WindowInfoHandle*>(ptr);
+ handle->decStrong((void*)android_view_InputWindowHandle_getHandle);
}
-
static const JNINativeMethod gInputWindowHandleMethods[] = {
/* name, signature, funcPtr */
{ "nativeDispose", "()V",
diff --git a/core/jni/android_hardware_input_InputWindowHandle.h b/core/jni/android_hardware_input_InputWindowHandle.h
index 408e0f1..aa375e9 100644
--- a/core/jni/android_hardware_input_InputWindowHandle.h
+++ b/core/jni/android_hardware_input_InputWindowHandle.h
@@ -24,24 +24,11 @@
namespace android {
-class NativeInputWindowHandle : public gui::WindowInfoHandle {
-public:
- NativeInputWindowHandle(jweak objWeak);
- virtual ~NativeInputWindowHandle();
+sp<gui::WindowInfoHandle> android_view_InputWindowHandle_getHandle(JNIEnv* env,
+ jobject inputWindowHandleObj);
- jobject getInputWindowHandleObjLocalRef(JNIEnv* env);
-
- virtual bool updateInfo();
-
-private:
- jweak mObjWeak;
-};
-
-extern sp<NativeInputWindowHandle> android_view_InputWindowHandle_getHandle(
- JNIEnv* env, jobject inputWindowHandleObj);
-
-extern jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env,
- gui::WindowInfo windowInfo);
+jobject android_view_InputWindowHandle_fromWindowInfo(JNIEnv* env,
+ const gui::WindowInfo& windowInfo);
} // namespace android
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 56292c3..d3bf36e 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -979,14 +979,16 @@
static void nativeSetInputWindowInfo(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jobject inputWindow) {
+ if (!inputWindow) {
+ jniThrowNullPointerException(env, "InputWindowHandle is null");
+ return;
+ }
+
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- sp<NativeInputWindowHandle> handle = android_view_InputWindowHandle_getHandle(
- env, inputWindow);
- handle->updateInfo();
-
+ sp<gui::WindowInfoHandle> info = android_view_InputWindowHandle_getHandle(env, inputWindow);
auto ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
- transaction->setInputWindowInfo(ctrl, *handle->getInfo());
+ transaction->setInputWindowInfo(ctrl, std::move(info));
}
static void nativeAddWindowInfosReportedListener(JNIEnv* env, jclass clazz, jlong transactionObj,
diff --git a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
index da1fffa..a2598f6 100644
--- a/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
+++ b/core/tests/coretests/src/android/app/PropertyInvalidatedCacheTests.java
@@ -16,6 +16,7 @@
package android.app;
+import static android.app.Flags.FLAG_PIC_ISOLATE_CACHE_BY_UID;
import static android.app.PropertyInvalidatedCache.NONCE_UNSET;
import static android.app.PropertyInvalidatedCache.MODULE_BLUETOOTH;
import static android.app.PropertyInvalidatedCache.MODULE_SYSTEM;
@@ -30,8 +31,9 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.app.PropertyInvalidatedCache.Args;
import android.annotation.SuppressLint;
+import android.app.PropertyInvalidatedCache.Args;
+import android.os.Binder;
import com.android.internal.os.ApplicationSharedMemory;
import android.platform.test.annotations.IgnoreUnderRavenwood;
@@ -58,6 +60,7 @@
*/
@SmallTest
public class PropertyInvalidatedCacheTests {
+ @Rule
public final CheckFlagsRule mCheckFlagsRule =
DeviceFlagsValueProvider.createCheckFlagsRule();
@@ -455,8 +458,9 @@
// Test the Args-style constructor.
@Test
public void testArgsConstructor() {
- // Create a cache with a maximum of four entries.
- TestCache cache = new TestCache(new Args(MODULE_TEST).api("init1").maxEntries(4),
+ // Create a cache with a maximum of four entries and non-isolated UIDs.
+ TestCache cache = new TestCache(new Args(MODULE_TEST)
+ .maxEntries(4).isolateUids(false).api("init1"),
new TestQuery());
cache.invalidateCache();
@@ -570,4 +574,73 @@
// Expected exception.
}
}
+
+ // Verify that a cache created with isolatedUids(true) separates out the results.
+ @RequiresFlagsEnabled(FLAG_PIC_ISOLATE_CACHE_BY_UID)
+ @Test
+ public void testIsolatedUids() {
+ TestCache cache = new TestCache(new Args(MODULE_TEST)
+ .maxEntries(4).isolateUids(true).api("testIsolatedUids").testMode(true),
+ new TestQuery());
+ cache.invalidateCache();
+ final int uid1 = 1;
+ final int uid2 = 2;
+
+ long token = Binder.setCallingWorkSourceUid(uid1);
+ try {
+ // Populate the cache for user 1
+ assertEquals("foo5", cache.query(5));
+ assertEquals(1, cache.getRecomputeCount());
+ assertEquals("foo5", cache.query(5));
+ assertEquals(1, cache.getRecomputeCount());
+ assertEquals("foo6", cache.query(6));
+ assertEquals(2, cache.getRecomputeCount());
+
+ // Populate the cache for user 2. User 1 values are not reused.
+ Binder.setCallingWorkSourceUid(uid2);
+ assertEquals("foo5", cache.query(5));
+ assertEquals(3, cache.getRecomputeCount());
+ assertEquals("foo5", cache.query(5));
+ assertEquals(3, cache.getRecomputeCount());
+
+ // Verify that the cache for user 1 is still populated.
+ Binder.setCallingWorkSourceUid(uid1);
+ assertEquals("foo5", cache.query(5));
+ assertEquals(3, cache.getRecomputeCount());
+
+ } finally {
+ Binder.restoreCallingWorkSource(token);
+ }
+
+ // Repeat the test with a non-isolated cache.
+ cache = new TestCache(new Args(MODULE_TEST)
+ .maxEntries(4).isolateUids(false).api("testIsolatedUids2").testMode(true),
+ new TestQuery());
+ cache.invalidateCache();
+ token = Binder.setCallingWorkSourceUid(uid1);
+ try {
+ // Populate the cache for user 1
+ assertEquals("foo5", cache.query(5));
+ assertEquals(1, cache.getRecomputeCount());
+ assertEquals("foo5", cache.query(5));
+ assertEquals(1, cache.getRecomputeCount());
+ assertEquals("foo6", cache.query(6));
+ assertEquals(2, cache.getRecomputeCount());
+
+ // Populate the cache for user 2. User 1 values are reused.
+ Binder.setCallingWorkSourceUid(uid2);
+ assertEquals("foo5", cache.query(5));
+ assertEquals(2, cache.getRecomputeCount());
+ assertEquals("foo5", cache.query(5));
+ assertEquals(2, cache.getRecomputeCount());
+
+ // Verify that the cache for user 1 is still populated.
+ Binder.setCallingWorkSourceUid(uid1);
+ assertEquals("foo5", cache.query(5));
+ assertEquals(2, cache.getRecomputeCount());
+
+ } finally {
+ Binder.restoreCallingWorkSource(token);
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/content/IntentTest.java b/core/tests/coretests/src/android/content/IntentTest.java
index 7bc4abd..d169ce3 100644
--- a/core/tests/coretests/src/android/content/IntentTest.java
+++ b/core/tests/coretests/src/android/content/IntentTest.java
@@ -37,10 +37,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-
/**
* Build/Install/Run:
* atest FrameworksCoreTests:IntentTest
@@ -61,12 +57,7 @@
public void testReadFromParcelWithExtraIntentKeys() {
Intent intent = new Intent("TEST_ACTION");
intent.putExtra(TEST_EXTRA_NAME, new Intent(TEST_ACTION));
- // Not an intent, don't count.
intent.putExtra(TEST_EXTRA_NAME + "2", 1);
- ArrayList<Intent> intents = new ArrayList<>();
- intents.add(new Intent(TEST_ACTION));
- intent.putParcelableArrayListExtra(TEST_EXTRA_NAME + "3", intents);
- intent.setClipData(ClipData.newIntent("label", new Intent(TEST_ACTION)));
intent.collectExtraIntentKeys();
final Parcel parcel = Parcel.obtain();
@@ -77,7 +68,7 @@
assertEquals(intent.getAction(), target.getAction());
assertEquals(intent.getExtraIntentKeys(), target.getExtraIntentKeys());
- assertThat(intent.getExtraIntentKeys()).hasSize(3);
+ assertThat(intent.getExtraIntentKeys()).hasSize(1);
}
@Test
@@ -96,37 +87,13 @@
@RequiresFlagsEnabled(Flags.FLAG_PREVENT_INTENT_REDIRECT)
public void testCollectExtraIntentKeys() {
Intent intent = new Intent(TEST_ACTION);
-
- Intent[] intents = new Intent[10];
- for (int i = 0; i < intents.length; i++) {
- intents[i] = new Intent("action" + i);
- }
- Intent[] intents2 = new Intent[2]; // intents[6-7]
- System.arraycopy(intents, 6, intents2, 0, intents2.length);
- ArrayList<Intent> intents3 = new ArrayList<>(2);
- intents3.addAll(Arrays.asList(intents).subList(8, 10)); // intents[8-9]
- intent.putExtra("key1", intents[0]);
- intent.putExtra("array-key", intents2);
- intent.setClipData(ClipData.newIntent("label2", intents[1]));
- intent.putExtra("intkey", 1);
- intents[0].putExtra("key3", intents[2]);
- intents[0].setClipData(ClipData.newIntent("label4", intents[3]));
- intents[0].putParcelableArrayListExtra("array-list-key", intents3);
- intents[1].putExtra("key3", intents[4]);
- intents[1].setClipData(ClipData.newIntent("label4", intents[5]));
- intents[5].putExtra("intkey", 2);
+ Intent extraIntent = new Intent(TEST_ACTION, TEST_URI);
+ intent.putExtra(TEST_EXTRA_NAME, extraIntent);
intent.collectExtraIntentKeys();
- // collect all actions of nested intents.
- final List<String> actions = new ArrayList<>();
- intent.forEachNestedCreatorToken(intent1 -> {
- actions.add(intent1.getAction());
- });
- assertThat(actions).hasSize(10);
- for (int i = 0; i < intents.length; i++) {
- assertThat(actions).contains("action" + i);
- }
+ assertThat(intent.getExtraIntentKeys()).hasSize(1);
+ assertThat(intent.getExtraIntentKeys()).contains(TEST_EXTRA_NAME);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 999ce17..1f77abe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -19,6 +19,7 @@
import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN;
import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_GESTURE;
+import static com.android.wm.shell.shared.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
import android.annotation.Nullable;
import android.content.Context;
@@ -66,8 +67,6 @@
private static final String TAG = BubbleBarLayerView.class.getSimpleName();
- private static final float SCRIM_ALPHA = 0.2f;
-
private final BubbleController mBubbleController;
private final BubbleData mBubbleData;
private final BubblePositioner mPositioner;
@@ -386,7 +385,7 @@
if (show) {
mScrimView.animate()
.setInterpolator(ALPHA_IN)
- .alpha(SCRIM_ALPHA)
+ .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA)
.start();
} else {
mScrimView.animate()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
index 4abb35c..193c593 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/pip/PipAppOpsListener.kt
@@ -16,8 +16,11 @@
package com.android.wm.shell.common.pip
import android.app.AppOpsManager
+import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
+import android.util.Pair
+import com.android.internal.annotations.VisibleForTesting
import com.android.wm.shell.common.ShellExecutor
class PipAppOpsListener(
@@ -27,10 +30,12 @@
) {
private val mAppOpsManager: AppOpsManager = checkNotNull(
mContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager)
+ private var mTopPipActivityInfoSupplier: (Context) -> Pair<ComponentName?, Int> =
+ PipUtils::getTopPipActivity
private val mAppOpsChangedListener = AppOpsManager.OnOpChangedListener { _, packageName ->
try {
// Dismiss the PiP once the user disables the app ops setting for that package
- val topPipActivityInfo = PipUtils.getTopPipActivity(mContext)
+ val topPipActivityInfo = mTopPipActivityInfoSupplier.invoke(mContext)
val componentName = topPipActivityInfo.first ?: return@OnOpChangedListener
val userId = topPipActivityInfo.second
val appInfo = mContext.packageManager
@@ -75,4 +80,9 @@
/** Dismisses the PIP window. */
fun dismissPip()
}
+
+ @VisibleForTesting
+ fun setTopPipActivityInfoSupplier(supplier: (Context) -> Pair<ComponentName?, Int>) {
+ mTopPipActivityInfoSupplier = supplier
+ }
}
\ No newline at end of file
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 a472f79..44fce81 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
@@ -836,14 +836,21 @@
@Provides
static Optional<DesktopImmersiveController> provideDesktopImmersiveController(
Context context,
+ ShellInit shellInit,
Transitions transitions,
@DynamicOverride DesktopRepository desktopRepository,
DisplayController displayController,
- ShellTaskOrganizer shellTaskOrganizer) {
+ ShellTaskOrganizer shellTaskOrganizer,
+ ShellCommandHandler shellCommandHandler) {
if (DesktopModeStatus.canEnterDesktopMode(context)) {
return Optional.of(
new DesktopImmersiveController(
- transitions, desktopRepository, displayController, shellTaskOrganizer));
+ shellInit,
+ transitions,
+ desktopRepository,
+ displayController,
+ shellTaskOrganizer,
+ shellCommandHandler));
}
return Optional.empty();
}
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 3a4764d..3cd5df3 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
@@ -19,6 +19,7 @@
import android.content.Context;
import android.os.Handler;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
@@ -41,6 +42,7 @@
import com.android.wm.shell.common.pip.SizeSpecSource;
import com.android.wm.shell.dagger.WMShellBaseModule;
import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipParamsChangedForwarder;
@@ -169,6 +171,8 @@
PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenControllerOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
+ Optional<DesktopRepository> desktopRepositoryOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -176,7 +180,8 @@
syncTransactionQueue, pipTransitionState, pipBoundsState, pipDisplayLayoutState,
pipBoundsAlgorithm, menuPhoneController, pipAnimationController,
pipSurfaceTransactionHelper, pipTransitionController, pipParamsChangedForwarder,
- splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
+ splitScreenControllerOptional, pipPerfHintControllerOptional,
+ desktopRepositoryOptional, rootTaskDisplayAreaOrganizer, displayController,
pipUiEventLogger, shellTaskOrganizer, mainExecutor);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
index 8d1b15c1..78e676f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/TvPipModule.java
@@ -22,6 +22,7 @@
import androidx.annotation.NonNull;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.WindowManagerShellWrapper;
import com.android.wm.shell.common.DisplayController;
@@ -214,6 +215,7 @@
PipSurfaceTransactionHelper pipSurfaceTransactionHelper,
Optional<SplitScreenController> splitScreenControllerOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
DisplayController displayController,
PipUiEventLogger pipUiEventLogger, ShellTaskOrganizer shellTaskOrganizer,
@ShellMainThread ShellExecutor mainExecutor) {
@@ -221,8 +223,9 @@
syncTransactionQueue, pipTransitionState, tvPipBoundsState, pipDisplayLayoutState,
tvPipBoundsAlgorithm, tvPipMenuController, pipAnimationController,
pipSurfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
- splitScreenControllerOptional, pipPerfHintControllerOptional, displayController,
- pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ splitScreenControllerOptional, pipPerfHintControllerOptional,
+ rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
index f69aa6d..1acde73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopImmersiveController.kt
@@ -34,10 +34,13 @@
import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.sysui.ShellCommandHandler
+import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.transition.Transitions.TransitionHandler
import com.android.wm.shell.transition.Transitions.TransitionObserver
import com.android.wm.shell.windowdecor.OnTaskResizeAnimationListener
+import java.io.PrintWriter
/**
* A controller to move tasks in/out of desktop's full immersive state where the task
@@ -45,27 +48,34 @@
* be transient below the status bar like in fullscreen immersive mode.
*/
class DesktopImmersiveController(
+ shellInit: ShellInit,
private val transitions: Transitions,
private val desktopRepository: DesktopRepository,
private val displayController: DisplayController,
private val shellTaskOrganizer: ShellTaskOrganizer,
+ private val shellCommandHandler: ShellCommandHandler,
private val transactionSupplier: () -> SurfaceControl.Transaction,
) : TransitionHandler, TransitionObserver {
constructor(
+ shellInit: ShellInit,
transitions: Transitions,
desktopRepository: DesktopRepository,
displayController: DisplayController,
shellTaskOrganizer: ShellTaskOrganizer,
+ shellCommandHandler: ShellCommandHandler,
) : this(
+ shellInit,
transitions,
desktopRepository,
displayController,
shellTaskOrganizer,
+ shellCommandHandler,
{ SurfaceControl.Transaction() }
)
- private var state: TransitionState? = null
+ @VisibleForTesting
+ var state: TransitionState? = null
@VisibleForTesting
val pendingExternalExitTransitions = mutableListOf<ExternalPendingExit>()
@@ -79,10 +89,21 @@
/** A listener to invoke on animation changes during entry/exit. */
var onTaskResizeAnimationListener: OnTaskResizeAnimationListener? = null
+ init {
+ shellInit.addInitCallback({ onInit() }, this)
+ }
+
+ fun onInit() {
+ shellCommandHandler.addDumpCallback(this::dump, this)
+ }
+
/** Starts a transition to enter full immersive state inside the desktop. */
fun moveTaskToImmersive(taskInfo: RunningTaskInfo) {
if (inProgress) {
- logV("Cannot start entry because transition already in progress.")
+ logV(
+ "Cannot start entry because transition(s) already in progress: %s",
+ getRunningTransitions()
+ )
return
}
val wct = WindowContainerTransaction().apply {
@@ -100,7 +121,10 @@
fun moveTaskToNonImmersive(taskInfo: RunningTaskInfo) {
if (inProgress) {
- logV("Cannot start exit because transition already in progress.")
+ logV(
+ "Cannot start exit because transition(s) already in progress: %s",
+ getRunningTransitions()
+ )
return
}
@@ -225,14 +249,19 @@
finishCallback: Transitions.TransitionFinishCallback
): Boolean {
val state = requireState()
- if (transition != state.transition) return false
+ check(state.transition == transition) {
+ "Transition $transition did not match expected state=$state"
+ }
logD("startAnimation transition=%s", transition)
animateResize(
targetTaskId = state.taskId,
info = info,
startTransaction = startTransaction,
finishTransaction = finishTransaction,
- finishCallback = finishCallback
+ finishCallback = {
+ finishCallback.onTransitionFinished(/* wct= */ null)
+ clearState()
+ },
)
return true
}
@@ -242,12 +271,18 @@
info: TransitionInfo,
startTransaction: SurfaceControl.Transaction,
finishTransaction: SurfaceControl.Transaction,
- finishCallback: Transitions.TransitionFinishCallback
+ finishCallback: Transitions.TransitionFinishCallback,
) {
logD("animateResize for task#%d", targetTaskId)
- val change = info.changes.first { c ->
+ val change = info.changes.firstOrNull { c ->
val taskInfo = c.taskInfo
- return@first taskInfo != null && taskInfo.taskId == targetTaskId
+ return@firstOrNull taskInfo != null && taskInfo.taskId == targetTaskId
+ }
+ if (change == null) {
+ logD("Did not find change for task#%d to animate", targetTaskId)
+ startTransaction.apply()
+ finishCallback.onTransitionFinished(/* wct= */ null)
+ return
}
animateResizeChange(change, startTransaction, finishTransaction, finishCallback)
}
@@ -288,7 +323,6 @@
.apply()
onTaskResizeAnimationListener?.onAnimationEnd(taskId)
finishCallback.onTransitionFinished(null /* wct */)
- clearState()
}
)
addUpdateListener { animation ->
@@ -357,8 +391,17 @@
// Check if this is a direct immersive enter/exit transition.
if (transition == state?.transition) {
val state = requireState()
- val startBounds = info.changes.first { c -> c.taskInfo?.taskId == state.taskId }
- .startAbsBounds
+ val immersiveChange = info.changes.firstOrNull { c ->
+ c.taskInfo?.taskId == state.taskId
+ }
+ if (immersiveChange == null) {
+ logV(
+ "Direct move for task#%d in %s direction missing immersive change.",
+ state.taskId, state.direction
+ )
+ return
+ }
+ val startBounds = immersiveChange.startAbsBounds
logV("Direct move for task#%d in %s direction verified", state.taskId, state.direction)
when (state.direction) {
Direction.ENTER -> {
@@ -446,11 +489,30 @@
private fun requireState(): TransitionState =
state ?: error("Expected non-null transition state")
+ private fun getRunningTransitions(): List<IBinder> {
+ val running = mutableListOf<IBinder>()
+ state?.let {
+ running.add(it.transition)
+ }
+ pendingExternalExitTransitions.forEach {
+ running.add(it.transition)
+ }
+ return running
+ }
+
private fun TransitionInfo.hasTaskChange(taskId: Int): Boolean =
changes.any { c -> c.taskInfo?.taskId == taskId }
+ private fun dump(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ pw.println("${prefix}DesktopImmersiveController")
+ pw.println(innerPrefix + "state=" + state)
+ pw.println(innerPrefix + "pendingExternalExitTransitions=" + pendingExternalExitTransitions)
+ }
+
/** The state of the currently running transition. */
- private data class TransitionState(
+ @VisibleForTesting
+ data class TransitionState(
val transition: IBinder,
val displayId: Int,
val taskId: Int,
@@ -483,7 +545,8 @@
fun asExit(): Exit? = if (this is Exit) this else null
}
- private enum class Direction {
+ @VisibleForTesting
+ enum class Direction {
ENTER, EXIT
}
@@ -495,9 +558,10 @@
ProtoLog.d(WM_SHELL_DESKTOP_MODE, "%s: $msg", TAG, *arguments)
}
- private companion object {
+ companion object {
private const val TAG = "DesktopImmersive"
- private const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
+ @VisibleForTesting
+ const val FULL_IMMERSIVE_ANIM_DURATION_MS = 336L
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
index fda709a..08ca55f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopRepository.kt
@@ -102,6 +102,9 @@
/* Tracks last bounds of task before toggled to stable bounds. */
private val boundsBeforeMaximizeByTaskId = SparseArray<Rect>()
+ /* Tracks last bounds of task before it is minimized. */
+ private val boundsBeforeMinimizeByTaskId = SparseArray<Rect>()
+
/* Tracks last bounds of task before toggled to immersive state. */
private val boundsBeforeFullImmersiveByTaskId = SparseArray<Rect>()
@@ -462,6 +465,14 @@
fun saveBoundsBeforeMaximize(taskId: Int, bounds: Rect) =
boundsBeforeMaximizeByTaskId.set(taskId, Rect(bounds))
+ /** Removes and returns the bounds saved before minimizing the given task. */
+ fun removeBoundsBeforeMinimize(taskId: Int): Rect? =
+ boundsBeforeMinimizeByTaskId.removeReturnOld(taskId)
+
+ /** Saves the bounds of the given task before minimizing. */
+ fun saveBoundsBeforeMinimize(taskId: Int, bounds: Rect?) =
+ boundsBeforeMinimizeByTaskId.set(taskId, Rect(bounds))
+
/** Removes and returns the bounds saved before entering immersive with the given task. */
fun removeBoundsBeforeFullImmersive(taskId: Int): Rect? =
boundsBeforeFullImmersiveByTaskId.removeReturnOld(taskId)
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 162879c..927fd88 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
@@ -1770,9 +1770,13 @@
transition: IBinder,
taskIdToMinimize: Int,
) {
- val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize) ?: return
+ val taskToMinimize = shellTaskOrganizer.getRunningTaskInfo(taskIdToMinimize)
desktopTasksLimiter.ifPresent {
- it.addPendingMinimizeChange(transition, taskToMinimize.displayId, taskToMinimize.taskId)
+ it.addPendingMinimizeChange(
+ transition = transition,
+ displayId = taskToMinimize?.displayId ?: DEFAULT_DISPLAY,
+ taskId = taskIdToMinimize
+ )
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
index f0e3a2b..77af627 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksLimiter.kt
@@ -92,6 +92,12 @@
}
taskToMinimize.transitionInfo = info
activeTransitionTokensAndTasks[transition] = taskToMinimize
+
+ // Save current bounds before minimizing in case we need to restore to it later.
+ val boundsBeforeMinimize = info.changes.find { change ->
+ change.taskInfo?.taskId == taskToMinimize.taskId }?.startAbsBounds
+ taskRepository.saveBoundsBeforeMinimize(taskToMinimize.taskId, boundsBeforeMinimize)
+
this@DesktopTasksLimiter.minimizeTask(
taskToMinimize.displayId, taskToMinimize.taskId)
}
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 c4e63df..86c826a 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
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -67,6 +68,7 @@
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.window.DisplayAreaInfo;
import android.window.TaskOrganizer;
import android.window.TaskSnapshot;
import android.window.WindowContainerToken;
@@ -74,7 +76,9 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.ProtoLog;
+import com.android.window.flags.Flags;
import com.android.wm.shell.R;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ScreenshotUtils;
@@ -87,6 +91,7 @@
import com.android.wm.shell.common.pip.PipPerfHintController;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
@@ -145,6 +150,8 @@
private final PipSurfaceTransactionHelper mSurfaceTransactionHelper;
private final Optional<SplitScreenController> mSplitScreenOptional;
@Nullable private final PipPerfHintController mPipPerfHintController;
+ private final Optional<DesktopRepository> mDesktopRepositoryOptional;
+ private final RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
protected final ShellTaskOrganizer mTaskOrganizer;
protected final ShellExecutor mMainExecutor;
@@ -388,6 +395,8 @@
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
+ Optional<DesktopRepository> desktopRepositoryOptional,
+ RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -414,6 +423,8 @@
new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory();
mSplitScreenOptional = splitScreenOptional;
mPipPerfHintController = pipPerfHintControllerOptional.orElse(null);
+ mDesktopRepositoryOptional = desktopRepositoryOptional;
+ mRootTaskDisplayAreaOrganizer = rootTaskDisplayAreaOrganizer;
mTaskOrganizer = shellTaskOrganizer;
mMainExecutor = mainExecutor;
@@ -741,10 +752,23 @@
}
/** Returns the bounds to restore to when exiting PIP mode. */
+ // TODO(b/377581840): Instead of manually tracking bounds, use bounds from Core.
public Rect getExitDestinationBounds() {
+ if (isPipLaunchedInDesktopMode()) {
+ final Rect freeformBounds = mDesktopRepositoryOptional.get().removeBoundsBeforeMinimize(
+ mTaskInfo.taskId);
+ return Objects.requireNonNullElseGet(freeformBounds, mPipBoundsState::getDisplayBounds);
+ }
return mPipBoundsState.getDisplayBounds();
}
+ /** Returns whether PiP was launched while in desktop mode. */
+ // TODO(377581840): Update this check to include non-minimized cases, e.g. split to PiP etc.
+ private boolean isPipLaunchedInDesktopMode() {
+ return Flags.enableDesktopWindowingPip() && mDesktopRepositoryOptional.isPresent()
+ && mDesktopRepositoryOptional.get().isMinimizedTask(mTaskInfo.taskId);
+ }
+
private void exitLaunchIntoPipTask(WindowContainerTransaction wct) {
wct.startTask(mTaskInfo.launchIntoPipHostTaskId, null /* ActivityOptions */);
mTaskOrganizer.applyTransaction(wct);
@@ -1808,7 +1832,25 @@
* and can be overridden to restore to an alternate windowing mode.
*/
public int getOutPipWindowingMode() {
- // By default, simply reset the windowing mode to undefined.
+ final DisplayAreaInfo tdaInfo = mRootTaskDisplayAreaOrganizer.getDisplayAreaInfo(
+ mTaskInfo.displayId);
+
+ // If PiP was launched while in desktop mode (we should return the task to freeform
+ // windowing mode):
+ // 1) If the display windowing mode is freeform, set windowing mode to undefined so it will
+ // resolve the windowing mode to the display's windowing mode.
+ // 2) If the display windowing mode is not freeform, set windowing mode to freeform.
+ if (tdaInfo != null && isPipLaunchedInDesktopMode()) {
+ final int displayWindowingMode =
+ tdaInfo.configuration.windowConfiguration.getWindowingMode();
+ if (displayWindowingMode == WINDOWING_MODE_FREEFORM) {
+ return WINDOWING_MODE_UNDEFINED;
+ } else {
+ return WINDOWING_MODE_FREEFORM;
+ }
+ }
+
+ // By default, or if the task is going to fullscreen, reset the windowing mode to undefined.
return WINDOWING_MODE_UNDEFINED;
}
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 28b91c6..8220ea5 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
@@ -530,6 +530,13 @@
if (mFixedRotationState != FIXED_ROTATION_TRANSITION
&& mFinishTransaction != null) {
mFinishTransaction.merge(tx);
+ // Set window crop and position to destination bounds to avoid flickering.
+ if (hasValidLeash) {
+ mFinishTransaction.setWindowCrop(leash, destinationBounds.width(),
+ destinationBounds.height());
+ mFinishTransaction.setPosition(leash, destinationBounds.left,
+ destinationBounds.top);
+ }
}
} else {
wct = new WindowContainerTransaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
index 614ef2a..fcba461 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipTaskOrganizer.java
@@ -21,6 +21,7 @@
import androidx.annotation.NonNull;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
@@ -61,6 +62,7 @@
@NonNull PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<SplitScreenController> splitScreenOptional,
Optional<PipPerfHintController> pipPerfHintControllerOptional,
+ @NonNull RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@NonNull DisplayController displayController,
@NonNull PipUiEventLogger pipUiEventLogger,
@NonNull ShellTaskOrganizer shellTaskOrganizer,
@@ -68,8 +70,9 @@
super(context, syncTransactionQueue, pipTransitionState, pipBoundsState,
pipDisplayLayoutState, boundsHandler, pipMenuController, pipAnimationController,
surfaceTransactionHelper, tvPipTransition, pipParamsChangedForwarder,
- splitScreenOptional, pipPerfHintControllerOptional, displayController,
- pipUiEventLogger, shellTaskOrganizer, mainExecutor);
+ splitScreenOptional, pipPerfHintControllerOptional, Optional.empty(),
+ rootTaskDisplayAreaOrganizer, displayController, pipUiEventLogger,
+ shellTaskOrganizer, mainExecutor);
mTvPipTransition = tvPipTransition;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
index ea783e9..3caad09 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java
@@ -20,6 +20,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
@@ -230,6 +231,11 @@
// If there is no PiP change, exit this transition handler and potentially try others.
if (pipChange == null) return false;
+ // Other targets might have default transforms applied that are not relevant when
+ // playing PiP transitions, so reset those transforms if needed.
+ prepareOtherTargetTransforms(info, startTransaction, finishTransaction);
+
+ // Update the PipTransitionState while supplying the PiP leash and token to be cached.
Bundle extra = new Bundle();
extra.putParcelable(PIP_TASK_TOKEN, pipChange.getContainer());
extra.putParcelable(PIP_TASK_LEASH, pipChange.getLeash());
@@ -341,17 +347,21 @@
(destinationBounds.height() - overlaySize) / 2f);
}
- final int startRotation = pipChange.getStartRotation();
- final int endRotation = mPipDisplayLayoutState.getRotation();
- final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
- : startRotation - endRotation;
+ final int delta = getFixedRotationDelta(info, pipChange);
if (delta != ROTATION_0) {
- mPipTransitionState.setInFixedRotation(true);
- handleBoundsEnterFixedRotation(pipChange, pipActivityChange, endRotation);
+ // Update transition target changes in place to prepare for fixed rotation.
+ handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange);
}
+ // Update the src-rect-hint in params in place, to set up initial animator transform.
+ Rect sourceRectHint = getAdjustedSourceRectHint(info, pipChange, pipActivityChange);
+ pipChange.getTaskInfo().pictureInPictureParams.getSourceRectHint().set(sourceRectHint);
+
+ // Config-at-end transitions need to have their activities transformed before starting
+ // the animation; this makes the buffer seem like it's been updated to final size.
prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
pipActivityChange);
+
startTransaction.merge(finishTransaction);
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
startTransaction, finishTransaction, destinationBounds, delta);
@@ -387,55 +397,36 @@
return false;
}
+ final SurfaceControl pipLeash = getLeash(pipChange);
final Rect startBounds = pipChange.getStartAbsBounds();
final Rect endBounds = pipChange.getEndAbsBounds();
-
final PictureInPictureParams params = pipChange.getTaskInfo().pictureInPictureParams;
- final float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params);
- final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds,
- endBounds);
- final Rect adjustedSourceRectHint = sourceRectHint != null ? new Rect(sourceRectHint)
- : PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio);
+ final Rect adjustedSourceRectHint = getAdjustedSourceRectHint(info, pipChange,
+ pipActivityChange);
- final SurfaceControl pipLeash = mPipTransitionState.getPinnedTaskLeash();
-
- // For opening type transitions, if there is a change of mode TO_FRONT/OPEN,
- // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f
- // by the Transitions framework to simplify Task opening transitions.
- if (TransitionUtil.isOpeningType(info.getType())) {
- for (TransitionInfo.Change change : info.getChanges()) {
- if (change.getLeash() == null) continue;
- if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
- startTransaction.setAlpha(change.getLeash(), 1f);
- }
- }
- }
-
- final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
- final int startRotation = pipChange.getStartRotation();
- final int endRotation = fixedRotationChange != null
- ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
- final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
- : startRotation - endRotation;
-
+ final int delta = getFixedRotationDelta(info, pipChange);
if (delta != ROTATION_0) {
- mPipTransitionState.setInFixedRotation(true);
- handleBoundsEnterFixedRotation(pipChange, pipActivityChange,
- fixedRotationChange.getEndFixedRotation());
+ // Update transition target changes in place to prepare for fixed rotation.
+ handleBoundsEnterFixedRotation(info, pipChange, pipActivityChange);
}
PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash,
startTransaction, finishTransaction, endBounds, delta);
- if (sourceRectHint == null) {
- // update the src-rect-hint in params in place, to set up initial animator transform.
- params.getSourceRectHint().set(adjustedSourceRectHint);
+ if (PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds, endBounds) == null) {
+ // If app provided src-rect-hint is invalid, use app icon overlay.
animator.setAppIconContentOverlay(
mContext, startBounds, endBounds, pipChange.getTaskInfo().topActivityInfo,
mPipBoundsState.getLauncherState().getAppIconSizePx());
}
+ // Update the src-rect-hint in params in place, to set up initial animator transform.
+ params.getSourceRectHint().set(adjustedSourceRectHint);
+
+ // Config-at-end transitions need to have their activities transformed before starting
+ // the animation; this makes the buffer seem like it's been updated to final size.
prepareConfigAtEndActivity(startTransaction, finishTransaction, pipChange,
pipActivityChange);
+
animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange));
animator.setAnimationEndCallback(() -> {
if (animator.getContentOverlayLeash() != null) {
@@ -457,11 +448,22 @@
animator.start();
}
- private void handleBoundsEnterFixedRotation(TransitionInfo.Change pipTaskChange,
- TransitionInfo.Change pipActivityChange, int endRotation) {
- final Rect endBounds = pipTaskChange.getEndAbsBounds();
- final Rect endActivityBounds = pipActivityChange.getEndAbsBounds();
- int startRotation = pipTaskChange.getStartRotation();
+ private void handleBoundsEnterFixedRotation(TransitionInfo info,
+ TransitionInfo.Change outPipTaskChange,
+ TransitionInfo.Change outPipActivityChange) {
+ final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+ final Rect endBounds = outPipTaskChange.getEndAbsBounds();
+ final Rect endActivityBounds = outPipActivityChange.getEndAbsBounds();
+ int startRotation = outPipTaskChange.getStartRotation();
+ int endRotation = fixedRotationChange != null
+ ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation();
+
+ if (startRotation == endRotation) {
+ return;
+ }
+
+ // This is used by display change listeners to respond properly to fixed rotation.
+ mPipTransitionState.setInFixedRotation(true);
// Cache the task to activity offset to potentially restore later.
Point activityEndOffset = new Point(endActivityBounds.left - endBounds.left,
@@ -490,15 +492,15 @@
endBounds.top + activityEndOffset.y);
}
- private void handleExpandFixedRotation(TransitionInfo.Change pipTaskChange, int endRotation) {
- final Rect endBounds = pipTaskChange.getEndAbsBounds();
+ private void handleExpandFixedRotation(TransitionInfo.Change outPipTaskChange, int delta) {
+ final Rect endBounds = outPipTaskChange.getEndAbsBounds();
final int width = endBounds.width();
final int height = endBounds.height();
final int left = endBounds.left;
final int top = endBounds.top;
int newTop, newLeft;
- if (endRotation == Surface.ROTATION_90) {
+ if (delta == Surface.ROTATION_90) {
newLeft = top;
newTop = -(left + width);
} else {
@@ -585,15 +587,11 @@
final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, endBounds,
startBounds);
- final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
- final int startRotation = pipChange.getStartRotation();
- final int endRotation = fixedRotationChange != null
- ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED;
- final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
- : endRotation - startRotation;
-
+ // We define delta = startRotation - endRotation, so we need to flip the sign.
+ final int delta = -getFixedRotationDelta(info, pipChange);
if (delta != ROTATION_0) {
- handleExpandFixedRotation(pipChange, endRotation);
+ // Update PiP target change in place to prepare for fixed rotation;
+ handleExpandFixedRotation(pipChange, delta);
}
PipExpandAnimator animator = new PipExpandAnimator(mContext, pipLeash,
@@ -661,6 +659,72 @@
return null;
}
+ @NonNull
+ private Rect getAdjustedSourceRectHint(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change pipTaskChange,
+ @NonNull TransitionInfo.Change pipActivityChange) {
+ final Rect startBounds = pipTaskChange.getStartAbsBounds();
+ final Rect endBounds = pipTaskChange.getEndAbsBounds();
+ final PictureInPictureParams params = pipTaskChange.getTaskInfo().pictureInPictureParams;
+
+ // Get the source-rect-hint provided by the app and check its validity; null if invalid.
+ final Rect sourceRectHint = PipBoundsAlgorithm.getValidSourceHintRect(params, startBounds,
+ endBounds);
+
+ final Rect adjustedSourceRectHint = new Rect();
+ if (sourceRectHint != null) {
+ adjustedSourceRectHint.set(sourceRectHint);
+ // If multi-activity PiP, use the parent task before PiP to retrieve display cutouts;
+ // then, offset the valid app provided source rect hint by the cutout insets.
+ // For single-activity PiP, just use the pinned task to get the cutouts instead.
+ TransitionInfo.Change parentBeforePip = pipActivityChange.getLastParent() != null
+ ? getChangeByToken(info, pipActivityChange.getLastParent()) : null;
+ Rect cutoutInsets = parentBeforePip != null
+ ? parentBeforePip.getTaskInfo().displayCutoutInsets
+ : pipTaskChange.getTaskInfo().displayCutoutInsets;
+ if (cutoutInsets != null
+ && getFixedRotationDelta(info, pipTaskChange) == ROTATION_90) {
+ adjustedSourceRectHint.offset(cutoutInsets.left, cutoutInsets.top);
+ }
+ } else {
+ // For non-valid app provided src-rect-hint, calculate one to crop into during
+ // app icon overlay animation.
+ float aspectRatio = mPipBoundsAlgorithm.getAspectRatioOrDefault(params);
+ adjustedSourceRectHint.set(
+ PipUtils.getEnterPipWithOverlaySrcRectHint(startBounds, aspectRatio));
+ }
+ return adjustedSourceRectHint;
+ }
+
+ @Surface.Rotation
+ private int getFixedRotationDelta(@NonNull TransitionInfo info,
+ @NonNull TransitionInfo.Change pipChange) {
+ TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info);
+ int startRotation = pipChange.getStartRotation();
+ int endRotation = fixedRotationChange != null
+ ? fixedRotationChange.getEndFixedRotation() : mPipDisplayLayoutState.getRotation();
+ int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0
+ : startRotation - endRotation;
+ return delta;
+ }
+
+ private void prepareOtherTargetTransforms(TransitionInfo info,
+ SurfaceControl.Transaction startTransaction,
+ SurfaceControl.Transaction finishTransaction) {
+ // For opening type transitions, if there is a change of mode TO_FRONT/OPEN,
+ // make sure that change has alpha of 1f, since it's init state might be set to alpha=0f
+ // by the Transitions framework to simplify Task opening transitions.
+ if (TransitionUtil.isOpeningType(info.getType())) {
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (change.getLeash() == null) continue;
+ if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
+ startTransaction.setAlpha(change.getLeash(), 1f);
+ }
+ }
+ }
+
+ }
+
private WindowContainerTransaction getEnterPipTransaction(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
// cache the original task token to check for multi-activity case later
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index cc0e1df..19a73f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -55,7 +55,6 @@
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER;
-import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_APP_FINISHED;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
@@ -1099,16 +1098,11 @@
void setSideStagePosition(@SplitPosition int sideStagePosition,
@Nullable WindowContainerTransaction wct) {
- setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
- }
-
- private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
- @Nullable WindowContainerTransaction wct) {
if (mSideStagePosition == sideStagePosition) return;
mSideStagePosition = sideStagePosition;
sendOnStagePositionChanged();
- if (mSideStage.mVisible && updateBounds) {
+ if (mSideStage.mVisible) {
if (wct == null) {
// onLayoutChanged builds/applies a wct with the contents of updateWindowBounds.
onLayoutSizeChanged(mSplitLayout);
@@ -1199,6 +1193,7 @@
if (!isSplitActive()) return;
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, wct);
applyExitSplitScreen(childrenToTop, wct, exitReason);
}
@@ -1598,6 +1593,13 @@
}
if (present) {
updateRecentTasksSplitPair();
+ } else if (mMainStage.getChildCount() == 0 && mSideStage.getChildCount() == 0) {
+ mRecentTasks.ifPresent(recentTasks -> {
+ // remove the split pair mapping from recentTasks, and disable further updates
+ // to splits in the recents until we enter split again.
+ recentTasks.removeSplitPair(taskId);
+ });
+ exitSplitScreen(mMainStage, EXIT_REASON_ROOT_TASK_VANISHED);
}
for (int i = mListeners.size() - 1; i >= 0; --i) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
new file mode 100644
index 0000000..b9490b8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/pip/PipAppOpsListenerTest.java
@@ -0,0 +1,191 @@
+/*
+ * 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.common.pip;
+
+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.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.AppOpsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.common.ShellExecutor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Unit test against {@link PipAppOpsListener}.
+ */
+@SmallTest
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner.class)
+public class PipAppOpsListenerTest {
+
+ @Mock private Context mMockContext;
+ @Mock private PackageManager mMockPackageManager;
+ @Mock private AppOpsManager mMockAppOpsManager;
+ @Mock private PipAppOpsListener.Callback mMockCallback;
+ @Mock private ShellExecutor mMockExecutor;
+
+ private PipAppOpsListener mPipAppOpsListener;
+
+ private ArgumentCaptor<AppOpsManager.OnOpChangedListener> mOnOpChangedListenerCaptor;
+ private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
+ private Pair<ComponentName, Integer> mTopPipActivity;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockContext.getSystemService(Context.APP_OPS_SERVICE))
+ .thenReturn(mMockAppOpsManager);
+ mOnOpChangedListenerCaptor = ArgumentCaptor.forClass(
+ AppOpsManager.OnOpChangedListener.class);
+ mRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
+ }
+
+ @Test
+ public void onActivityPinned_registerAppOpsListener() {
+ String packageName = "com.android.test.pip";
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+
+ mPipAppOpsListener.onActivityPinned(packageName);
+
+ verify(mMockAppOpsManager).startWatchingMode(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+ any(AppOpsManager.OnOpChangedListener.class));
+ }
+
+ @Test
+ public void onActivityUnpinned_unregisterAppOpsListener() {
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+
+ mPipAppOpsListener.onActivityUnpinned();
+
+ verify(mMockAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class));
+ }
+
+ @Test
+ public void disablePipAppOps_dismissPip() throws PackageManager.NameNotFoundException {
+ String packageName = "com.android.test.pip";
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+ // Set up the top pip activity info as mTopPipActivity
+ mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0);
+ mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity);
+ // Set up the application info as mApplicationInfo
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ // Mock the mode to be **not** allowed
+ when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName)))
+ .thenReturn(AppOpsManager.MODE_DEFAULT);
+ // Set up the initial state
+ mPipAppOpsListener.onActivityPinned(packageName);
+ verify(mMockAppOpsManager).startWatchingMode(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+ mOnOpChangedListenerCaptor.capture());
+ AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue();
+
+ opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
+ packageName);
+
+ verify(mMockExecutor).execute(mRunnableArgumentCaptor.capture());
+ Runnable runnable = mRunnableArgumentCaptor.getValue();
+ runnable.run();
+ verify(mMockCallback).dismissPip();
+ }
+
+ @Test
+ public void disablePipAppOps_differentPackage_doNothing()
+ throws PackageManager.NameNotFoundException {
+ String packageName = "com.android.test.pip";
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+ // Set up the top pip activity info as mTopPipActivity
+ mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0);
+ mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity);
+ // Set up the application info as mApplicationInfo
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName + ".modified";
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenReturn(applicationInfo);
+ // Mock the mode to be **not** allowed
+ when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName)))
+ .thenReturn(AppOpsManager.MODE_DEFAULT);
+ // Set up the initial state
+ mPipAppOpsListener.onActivityPinned(packageName);
+ verify(mMockAppOpsManager).startWatchingMode(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+ mOnOpChangedListenerCaptor.capture());
+ AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue();
+
+ opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
+ packageName);
+
+ verifyZeroInteractions(mMockExecutor);
+ }
+
+ @Test
+ public void disablePipAppOps_nameNotFound_unregisterAppOpsListener()
+ throws PackageManager.NameNotFoundException {
+ String packageName = "com.android.test.pip";
+ mPipAppOpsListener = new PipAppOpsListener(mMockContext, mMockCallback, mMockExecutor);
+ // Set up the top pip activity info as mTopPipActivity
+ mTopPipActivity = new Pair<>(new ComponentName(packageName, "PipActivity"), 0);
+ mPipAppOpsListener.setTopPipActivityInfoSupplier(this::getTopPipActivity);
+ // Set up the application info as mApplicationInfo
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName;
+ when(mMockPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
+ .thenThrow(PackageManager.NameNotFoundException.class);
+ // Mock the mode to be **not** allowed
+ when(mMockAppOpsManager.checkOpNoThrow(anyInt(), anyInt(), eq(packageName)))
+ .thenReturn(AppOpsManager.MODE_DEFAULT);
+ // Set up the initial state
+ mPipAppOpsListener.onActivityPinned(packageName);
+ verify(mMockAppOpsManager).startWatchingMode(
+ eq(AppOpsManager.OP_PICTURE_IN_PICTURE), eq(packageName),
+ mOnOpChangedListenerCaptor.capture());
+ AppOpsManager.OnOpChangedListener opChangedListener = mOnOpChangedListenerCaptor.getValue();
+
+ opChangedListener.onOpChanged(String.valueOf(AppOpsManager.OP_PICTURE_IN_PICTURE),
+ packageName);
+
+ verify(mMockAppOpsManager).stopWatchingMode(any(AppOpsManager.OnOpChangedListener.class));
+ }
+
+ private Pair<ComponentName, Integer> getTopPipActivity(Context context) {
+ return mTopPipActivity;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
index e05a0b5..a4f4d05 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopImmersiveControllerTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.wm.shell.desktopmode
+import android.animation.AnimatorTestRule
import android.app.ActivityManager.RunningTaskInfo
import android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS
import android.graphics.Rect
@@ -24,6 +25,7 @@
import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
import android.view.Display.DEFAULT_DISPLAY
import android.view.Surface
import android.view.SurfaceControl
@@ -43,6 +45,7 @@
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.util.StubTransaction
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Rule
@@ -64,17 +67,19 @@
* Usage: atest WMShellUnitTests:DesktopImmersiveControllerTest
*/
@SmallTest
+@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner::class)
class DesktopImmersiveControllerTest : ShellTestCase() {
@JvmField @Rule val setFlagsRule = SetFlagsRule()
+ @JvmField @Rule val animatorTestRule = AnimatorTestRule(this)
@Mock private lateinit var mockTransitions: Transitions
private lateinit var desktopRepository: DesktopRepository
@Mock private lateinit var mockDisplayController: DisplayController
@Mock private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
@Mock private lateinit var mockDisplayLayout: DisplayLayout
- private val transactionSupplier = { SurfaceControl.Transaction() }
+ private val transactionSupplier = { StubTransaction() }
private lateinit var controller: DesktopImmersiveController
@@ -89,10 +94,12 @@
(invocation.getArgument(0) as Rect).set(STABLE_BOUNDS)
}
controller = DesktopImmersiveController(
+ shellInit = mock(),
transitions = mockTransitions,
desktopRepository = desktopRepository,
displayController = mockDisplayController,
shellTaskOrganizer = mockShellTaskOrganizer,
+ shellCommandHandler = mock(),
transactionSupplier = transactionSupplier,
)
}
@@ -672,6 +679,60 @@
assertThat(controller.isImmersiveChange(transition, change)).isTrue()
}
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun externalAnimateResizeChange_doesNotCleanUpPendingTransitionState() {
+ val task = createFreeformTask()
+ val mockBinder = mock(IBinder::class.java)
+ whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)))
+ .thenReturn(mockBinder)
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = task.displayId,
+ taskId = task.taskId,
+ immersive = true
+ )
+
+ controller.moveTaskToNonImmersive(task)
+
+ controller.animateResizeChange(
+ change = TransitionInfo.Change(task.token, SurfaceControl()).apply {
+ taskInfo = task
+ },
+ startTransaction = StubTransaction(),
+ finishTransaction = StubTransaction(),
+ finishCallback = { }
+ )
+ animatorTestRule.advanceTimeBy(DesktopImmersiveController.FULL_IMMERSIVE_ANIM_DURATION_MS)
+
+ assertThat(controller.state).isNotNull()
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_ENABLE_FULLY_IMMERSIVE_IN_DESKTOP)
+ fun startAnimation_missingChange_clearsState() {
+ val task = createFreeformTask()
+ val mockBinder = mock(IBinder::class.java)
+ whenever(mockTransitions.startTransition(eq(TRANSIT_CHANGE), any(), eq(controller)))
+ .thenReturn(mockBinder)
+ desktopRepository.setTaskInFullImmersiveState(
+ displayId = task.displayId,
+ taskId = task.taskId,
+ immersive = false
+ )
+
+ controller.moveTaskToImmersive(task)
+
+ controller.startAnimation(
+ transition = mockBinder,
+ info = createTransitionInfo(changes = emptyList()),
+ startTransaction = StubTransaction(),
+ finishTransaction = StubTransaction(),
+ finishCallback = {}
+ )
+
+ assertThat(controller.state).isNull()
+ }
+
private fun createTransitionInfo(
@TransitionType type: Int = TRANSIT_CHANGE,
@TransitionFlags flags: Int = 0,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
index 414c1a6..7f790d5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopRepositoryTest.kt
@@ -936,6 +936,28 @@
}
@Test
+ fun saveBoundsBeforeMinimize_boundsSavedByTaskId() {
+ val taskId = 1
+ val bounds = Rect(0, 0, 200, 200)
+
+ repo.saveBoundsBeforeMinimize(taskId, bounds)
+
+ assertThat(repo.removeBoundsBeforeMinimize(taskId)).isEqualTo(bounds)
+ }
+
+ @Test
+ fun removeBoundsBeforeMinimize_returnsNullAfterBoundsRemoved() {
+ val taskId = 1
+ val bounds = Rect(0, 0, 200, 200)
+ repo.saveBoundsBeforeMinimize(taskId, bounds)
+ repo.removeBoundsBeforeMinimize(taskId)
+
+ val boundsBeforeMinimize = repo.removeBoundsBeforeMinimize(taskId)
+
+ assertThat(boundsBeforeMinimize).isNull()
+ }
+
+ @Test
fun getExpandedTasksOrdered_returnsFreeformTasksInCorrectOrder() {
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 3, isVisible = true)
repo.addTask(displayId = DEFAULT_DISPLAY, taskId = 2, isVisible = true)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
index 01b69ae..456b50d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksLimiterTest.kt
@@ -17,6 +17,7 @@
package com.android.wm.shell.desktopmode
import android.app.ActivityManager.RunningTaskInfo
+import android.graphics.Rect
import android.os.Binder
import android.os.Handler
import android.platform.test.annotations.DisableFlags
@@ -24,8 +25,10 @@
import android.platform.test.flag.junit.SetFlagsRule
import android.testing.AndroidTestingRunner
import android.view.Display.DEFAULT_DISPLAY
+import android.view.SurfaceControl
import android.view.WindowManager.TRANSIT_OPEN
import android.view.WindowManager.TRANSIT_TO_BACK
+import android.window.TransitionInfo
import android.window.WindowContainerTransaction
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK
import android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER
@@ -63,6 +66,7 @@
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
import org.mockito.Mockito.spy
import org.mockito.Mockito.`when`
import org.mockito.kotlin.eq
@@ -235,6 +239,30 @@
}
@Test
+ fun onTransitionReady_pendingTransition_changeTaskToBack_boundsSaved() {
+ val bounds = Rect(0, 0, 200, 200)
+ val transition = Binder()
+ val task = setUpFreeformTask()
+ desktopTasksLimiter.addPendingMinimizeChange(
+ transition, displayId = DEFAULT_DISPLAY, taskId = task.taskId)
+
+ val change = TransitionInfo.Change(task.token, mock(SurfaceControl::class.java)).apply {
+ mode = TRANSIT_TO_BACK
+ taskInfo = task
+ setStartAbsBounds(bounds)
+ }
+ desktopTasksLimiter.getTransitionObserver().onTransitionReady(
+ transition,
+ TransitionInfo(TRANSIT_OPEN, TransitionInfo.FLAG_NONE).apply { addChange(change) },
+ StubTransaction() /* startTransaction */,
+ StubTransaction() /* finishTransaction */)
+
+ assertThat(desktopTaskRepo.isMinimizedTask(taskId = task.taskId)).isTrue()
+ assertThat(desktopTaskRepo.removeBoundsBeforeMinimize(taskId = task.taskId)).isEqualTo(
+ bounds)
+ }
+
+ @Test
fun onTransitionReady_transitionMergedFromPending_taskIsMinimized() {
val mergedTransition = Binder()
val newTransition = Binder()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index bcb7461..5f58265 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -47,6 +47,7 @@
import androidx.test.filters.SmallTest;
import com.android.wm.shell.MockSurfaceControlHelper;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
@@ -61,6 +62,7 @@
import com.android.wm.shell.common.pip.PipSnapAlgorithm;
import com.android.wm.shell.common.pip.PipUiEventLogger;
import com.android.wm.shell.common.pip.SizeSpecSource;
+import com.android.wm.shell.desktopmode.DesktopRepository;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -90,6 +92,8 @@
@Mock private PipSurfaceTransactionHelper mMockPipSurfaceTransactionHelper;
@Mock private PipUiEventLogger mMockPipUiEventLogger;
@Mock private Optional<SplitScreenController> mMockOptionalSplitScreen;
+ @Mock private Optional<DesktopRepository> mMockOptionalDesktopRepository;
+ @Mock private RootTaskDisplayAreaOrganizer mRootTaskDisplayAreaOrganizer;
@Mock private ShellTaskOrganizer mMockShellTaskOrganizer;
@Mock private PipParamsChangedForwarder mMockPipParamsChangedForwarder;
private TestShellExecutor mMainExecutor;
@@ -120,8 +124,10 @@
mPipBoundsAlgorithm, mMockPhonePipMenuController, mMockPipAnimationController,
mMockPipSurfaceTransactionHelper, mMockPipTransitionController,
mMockPipParamsChangedForwarder, mMockOptionalSplitScreen,
- Optional.empty() /* pipPerfHintControllerOptional */, mMockDisplayController,
- mMockPipUiEventLogger, mMockShellTaskOrganizer, mMainExecutor);
+ Optional.empty() /* pipPerfHintControllerOptional */,
+ mMockOptionalDesktopRepository, mRootTaskDisplayAreaOrganizer,
+ mMockDisplayController, mMockPipUiEventLogger, mMockShellTaskOrganizer,
+ mMainExecutor);
mMainExecutor.flushAll();
preparePipTaskOrg();
preparePipSurfaceTransactionHelper();
diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt
index f56667d..de40209 100644
--- a/libs/appfunctions/api/current.txt
+++ b/libs/appfunctions/api/current.txt
@@ -59,7 +59,7 @@
ctor public ExecuteAppFunctionResponse(@NonNull android.app.appsearch.GenericDocument, @NonNull android.os.Bundle);
method @NonNull public android.os.Bundle getExtras();
method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
- field public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
+ field public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
}
}
diff --git a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
index 42c3c03..0826f04 100644
--- a/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
+++ b/libs/appfunctions/java/com/android/extensions/appfunctions/ExecuteAppFunctionResponse.java
@@ -39,7 +39,7 @@
*
* <p>See {@link #getResultDocument} for more information on extracting the return value.
*/
- public static final String PROPERTY_RETURN_VALUE = "android_app_appfunctions_returnvalue";
+ public static final String PROPERTY_RETURN_VALUE = "androidAppfunctionsReturnValue";
/**
* Returns the return value of the executed function.
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 52a21e2..ba2398c 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -51,7 +51,7 @@
is_exported: true
namespace: "media_tv"
description: "Enables the following type constant in MediaRoute2Info: LINE_ANALOG, LINE_DIGITAL, AUX_LINE"
- bug: "301713440"
+ bug: "375691732"
}
flag {
diff --git a/media/java/android/media/quality/AmbientBacklightSettings.java b/media/java/android/media/quality/AmbientBacklightSettings.java
index 391eb22..bb782bf 100644
--- a/media/java/android/media/quality/AmbientBacklightSettings.java
+++ b/media/java/android/media/quality/AmbientBacklightSettings.java
@@ -26,6 +26,7 @@
import java.lang.annotation.RetentionPolicy;
/**
+ * Settings for ambient backlight.
* @hide
*/
public class AmbientBacklightSettings implements Parcelable {
diff --git a/media/java/android/media/quality/IMediaQualityManager.aidl b/media/java/android/media/quality/IMediaQualityManager.aidl
index e6c79dd..250d59b 100644
--- a/media/java/android/media/quality/IMediaQualityManager.aidl
+++ b/media/java/android/media/quality/IMediaQualityManager.aidl
@@ -30,20 +30,22 @@
*/
interface IMediaQualityManager {
PictureProfile createPictureProfile(in PictureProfile pp);
- void updatePictureProfile(in long id, in PictureProfile pp);
- void removePictureProfile(in long id);
- PictureProfile getPictureProfileById(in long id);
+ void updatePictureProfile(in String id, in PictureProfile pp);
+ void removePictureProfile(in String id);
+ PictureProfile getPictureProfile(in int type, in String name);
List<PictureProfile> getPictureProfilesByPackage(in String packageName);
List<PictureProfile> getAvailablePictureProfiles();
- List<PictureProfile> getAllPictureProfiles();
+ List<String> getPictureProfilePackageNames();
+ List<String> getPictureProfileAllowList();
+ void setPictureProfileAllowList(in List<String> packages);
SoundProfile createSoundProfile(in SoundProfile pp);
- void updateSoundProfile(in long id, in SoundProfile pp);
- void removeSoundProfile(in long id);
- SoundProfile getSoundProfileById(in long id);
+ void updateSoundProfile(in String id, in SoundProfile pp);
+ void removeSoundProfile(in String id);
+ SoundProfile getSoundProfileById(in String id);
List<SoundProfile> getSoundProfilesByPackage(in String packageName);
List<SoundProfile> getAvailableSoundProfiles();
- List<SoundProfile> getAllSoundProfiles();
+ List<String> getSoundProfilePackageNames();
void registerPictureProfileCallback(in IPictureProfileCallback cb);
void registerSoundProfileCallback(in ISoundProfileCallback cb);
diff --git a/media/java/android/media/quality/IPictureProfileCallback.aidl b/media/java/android/media/quality/IPictureProfileCallback.aidl
index 05441cd..34aa2b0 100644
--- a/media/java/android/media/quality/IPictureProfileCallback.aidl
+++ b/media/java/android/media/quality/IPictureProfileCallback.aidl
@@ -17,6 +17,7 @@
package android.media.quality;
+import android.media.quality.ParamCapability;
import android.media.quality.PictureProfile;
/**
@@ -24,7 +25,9 @@
* @hide
*/
oneway interface IPictureProfileCallback {
- void onPictureProfileAdded(in long id, in PictureProfile p);
- void onPictureProfileUpdated(in long id, in PictureProfile p);
- void onPictureProfileRemoved(in long id, in PictureProfile p);
+ void onPictureProfileAdded(in String id, in PictureProfile p);
+ void onPictureProfileUpdated(in String id, in PictureProfile p);
+ void onPictureProfileRemoved(in String id, in PictureProfile p);
+ void onParamCapabilitiesChanged(in String id, in List<ParamCapability> caps);
+ void onError(in int err);
}
diff --git a/media/java/android/media/quality/MediaQualityManager.java b/media/java/android/media/quality/MediaQualityManager.java
index 38a2025..26d83ac 100644
--- a/media/java/android/media/quality/MediaQualityManager.java
+++ b/media/java/android/media/quality/MediaQualityManager.java
@@ -19,6 +19,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.FlaggedApi;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemService;
import android.content.Context;
import android.media.tv.flags.Flags;
@@ -63,7 +64,7 @@
mService = service;
IPictureProfileCallback ppCallback = new IPictureProfileCallback.Stub() {
@Override
- public void onPictureProfileAdded(long profileId, PictureProfile profile) {
+ public void onPictureProfileAdded(String profileId, PictureProfile profile) {
synchronized (mLock) {
for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
// TODO: filter callback record
@@ -72,7 +73,7 @@
}
}
@Override
- public void onPictureProfileUpdated(long profileId, PictureProfile profile) {
+ public void onPictureProfileUpdated(String profileId, PictureProfile profile) {
synchronized (mLock) {
for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
// TODO: filter callback record
@@ -81,7 +82,7 @@
}
}
@Override
- public void onPictureProfileRemoved(long profileId, PictureProfile profile) {
+ public void onPictureProfileRemoved(String profileId, PictureProfile profile) {
synchronized (mLock) {
for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
// TODO: filter callback record
@@ -89,6 +90,24 @@
}
}
}
+ @Override
+ public void onParamCapabilitiesChanged(String profileId, List<ParamCapability> caps) {
+ synchronized (mLock) {
+ for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+ // TODO: filter callback record
+ record.postParamCapabilitiesChanged(profileId, caps);
+ }
+ }
+ }
+ @Override
+ public void onError(int err) {
+ synchronized (mLock) {
+ for (PictureProfileCallbackRecord record : mPpCallbackRecords) {
+ // TODO: filter callback record
+ record.postError(err);
+ }
+ }
+ }
};
ISoundProfileCallback spCallback = new ISoundProfileCallback.Stub() {
@Override
@@ -175,14 +194,17 @@
/**
- * Gets picture profile by given profile ID.
- * @return the corresponding picture profile if available; {@code null} if the ID doesn't
- * exist or the profile is not accessible to the caller.
+ * Gets picture profile by given profile type and name.
+ *
+ * @return the corresponding picture profile if available; {@code null} if the name doesn't
+ * exist.
* @hide
*/
- public PictureProfile getPictureProfileById(long profileId) {
+ @Nullable
+ public PictureProfile getPictureProfile(
+ @PictureProfile.ProfileType int type, @NonNull String name) {
try {
- return mService.getPictureProfileById(profileId);
+ return mService.getPictureProfile(type, name);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -190,11 +212,13 @@
/**
- * @SystemApi gets profiles that available to the given package
- * @hide
+ * Gets profiles that available to the given package.
+ *
+ * @hide @SystemApi
*/
+ @NonNull
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
- public List<PictureProfile> getPictureProfilesByPackage(String packageName) {
+ public List<PictureProfile> getPictureProfilesByPackage(@NonNull String packageName) {
try {
return mService.getPictureProfilesByPackage(packageName);
} catch (RemoteException e) {
@@ -215,13 +239,16 @@
}
/**
- * @SystemApi all stored picture profiles of all packages
- * @hide
+ * Gets all package names whose picture profiles are available.
+ *
+ * @see #getPictureProfilesByPackage(String)
+ * @hide @SystemApi
*/
+ @NonNull
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
- public List<PictureProfile> getAllPictureProfiles() {
+ public List<String> getPictureProfilePackageNames() {
try {
- return mService.getAllPictureProfiles();
+ return mService.getPictureProfilePackageNames();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -231,10 +258,12 @@
/**
* Creates a picture profile and store it in the system.
*
- * @return the stored profile with an assigned profile ID.
+ * @return the stored profile with an assigned profile ID. {@code null} if it's not created
+ * successfully.
* @hide
*/
- public PictureProfile createPictureProfile(PictureProfile pp) {
+ @Nullable
+ public PictureProfile createPictureProfile(@NonNull PictureProfile pp) {
try {
return mService.createPictureProfile(pp);
} catch (RemoteException e) {
@@ -247,7 +276,7 @@
* Updates an existing picture profile and store it in the system.
* @hide
*/
- public void updatePictureProfile(long profileId, PictureProfile pp) {
+ public void updatePictureProfile(@NonNull String profileId, @NonNull PictureProfile pp) {
try {
mService.updatePictureProfile(profileId, pp);
} catch (RemoteException e) {
@@ -260,7 +289,7 @@
* Removes a picture profile from the system.
* @hide
*/
- public void removePictureProfile(long profileId) {
+ public void removePictureProfile(@NonNull String profileId) {
try {
mService.removePictureProfile(profileId);
} catch (RemoteException e) {
@@ -307,7 +336,7 @@
* exist or the profile is not accessible to the caller.
* @hide
*/
- public SoundProfile getSoundProfileById(long profileId) {
+ public SoundProfile getSoundProfileById(String profileId) {
try {
return mService.getSoundProfileById(profileId);
} catch (RemoteException e) {
@@ -346,9 +375,9 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_SOUND_QUALITY_SERVICE)
- public List<SoundProfile> getAllSoundProfiles() {
+ public List<String> getSoundProfilePackageNames() {
try {
- return mService.getAllSoundProfiles();
+ return mService.getSoundProfilePackageNames();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -374,7 +403,7 @@
* Updates an existing sound profile and store it in the system.
* @hide
*/
- public void updateSoundProfile(long profileId, SoundProfile sp) {
+ public void updateSoundProfile(String profileId, SoundProfile sp) {
try {
mService.updateSoundProfile(profileId, sp);
} catch (RemoteException e) {
@@ -387,7 +416,7 @@
* Removes a sound profile from the system.
* @hide
*/
- public void removeSoundProfile(long profileId) {
+ public void removeSoundProfile(String profileId) {
try {
mService.removeSoundProfile(profileId);
} catch (RemoteException e) {
@@ -399,7 +428,8 @@
* Gets capability information of the given parameters.
* @hide
*/
- public List<ParamCapability> getParamCapabilities(List<String> names) {
+ @NonNull
+ public List<ParamCapability> getParamCapabilities(@NonNull List<String> names) {
try {
return mService.getParamCapabilities(names);
} catch (RemoteException e) {
@@ -408,7 +438,38 @@
}
/**
+ * Gets the allowlist of packages that can create and removed picture profiles
+ *
+ * @see #createPictureProfile(PictureProfile)
+ * @see #removePictureProfile(String)
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+ @NonNull
+ public List<String> getPictureProfileAllowList() {
+ try {
+ return mService.getPictureProfileAllowList();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Sets the allowlist of packages that can create and removed picture profiles
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_GLOBAL_PICTURE_QUALITY_SERVICE)
+ public void setPictureProfileAllowList(@NonNull List<String> packageNames) {
+ try {
+ mService.setPictureProfileAllowList(packageNames);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns {@code true} if media quality HAL is implemented; {@code false} otherwise.
+ * @hide
*/
public boolean isSupported() {
try {
@@ -581,7 +642,7 @@
return mCallback;
}
- public void postPictureProfileAdded(final long id, PictureProfile profile) {
+ public void postPictureProfileAdded(final String id, PictureProfile profile) {
mExecutor.execute(new Runnable() {
@Override
@@ -591,7 +652,7 @@
});
}
- public void postPictureProfileUpdated(final long id, PictureProfile profile) {
+ public void postPictureProfileUpdated(final String id, PictureProfile profile) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -600,7 +661,7 @@
});
}
- public void postPictureProfileRemoved(final long id, PictureProfile profile) {
+ public void postPictureProfileRemoved(final String id, PictureProfile profile) {
mExecutor.execute(new Runnable() {
@Override
public void run() {
@@ -608,6 +669,24 @@
}
});
}
+
+ public void postParamCapabilitiesChanged(final String id, List<ParamCapability> caps) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onParamCapabilitiesChanged(id, caps);
+ }
+ });
+ }
+
+ public void postError(int error) {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ mCallback.onError(error);
+ }
+ });
+ }
}
private static final class SoundProfileCallbackRecord {
@@ -681,24 +760,57 @@
*/
public abstract static class PictureProfileCallback {
/**
+ * This is invoked when a picture profile has been added.
+ *
+ * @param profileId the ID of the profile.
+ * @param profile the newly added profile.
* @hide
*/
- public void onPictureProfileAdded(long id, PictureProfile profile) {
+ public void onPictureProfileAdded(
+ @NonNull String profileId, @NonNull PictureProfile profile) {
}
+
/**
+ * This is invoked when a picture profile has been updated.
+ *
+ * @param profileId the ID of the profile.
+ * @param profile the profile with updated info.
* @hide
*/
- public void onPictureProfileUpdated(long id, PictureProfile profile) {
+ public void onPictureProfileUpdated(
+ @NonNull String profileId, @NonNull PictureProfile profile) {
}
+
/**
+ * This is invoked when a picture profile has been removed.
+ *
+ * @param profileId the ID of the profile.
+ * @param profile the removed profile.
* @hide
*/
- public void onPictureProfileRemoved(long id, PictureProfile profile) {
+ public void onPictureProfileRemoved(
+ @NonNull String profileId, @NonNull PictureProfile profile) {
}
+
/**
+ * This is invoked when an issue has occurred.
+ *
+ * @param errorCode the error code
* @hide
*/
- public void onError(int errorCode) {
+ public void onError(@PictureProfile.ErrorCode int errorCode) {
+ }
+
+ /**
+ * This is invoked when parameter capabilities has been changed due to status changes of the
+ * content.
+ *
+ * @param profileId the ID of the profile used by the media content.
+ * @param updatedCaps the updated capabilities.
+ * @hide
+ */
+ public void onParamCapabilitiesChanged(
+ @NonNull String profileId, @NonNull List<ParamCapability> updatedCaps) {
}
}
diff --git a/media/java/android/media/quality/ParamCapability.java b/media/java/android/media/quality/ParamCapability.java
index 70e8592..0b698a9 100644
--- a/media/java/android/media/quality/ParamCapability.java
+++ b/media/java/android/media/quality/ParamCapability.java
@@ -34,7 +34,7 @@
* @hide
*/
@FlaggedApi(Flags.FLAG_MEDIA_QUALITY_FW)
-public class ParamCapability implements Parcelable {
+public final class ParamCapability implements Parcelable {
/** @hide */
@IntDef(flag = true, prefix = { "TYPE_" }, value = {
@@ -104,6 +104,7 @@
@NonNull
private final Bundle mCaps;
+ /** @hide */
protected ParamCapability(Parcel in) {
mName = in.readString();
mIsSupported = in.readBoolean();
@@ -112,7 +113,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mName);
dest.writeBoolean(mIsSupported);
dest.writeInt(mType);
@@ -124,6 +125,7 @@
return 0;
}
+ @NonNull
public static final Creator<ParamCapability> CREATOR = new Creator<ParamCapability>() {
@Override
public ParamCapability createFromParcel(Parcel in) {
diff --git a/media/java/android/media/quality/PictureProfile.java b/media/java/android/media/quality/PictureProfile.java
index 8fb5712..2be47dd 100644
--- a/media/java/android/media/quality/PictureProfile.java
+++ b/media/java/android/media/quality/PictureProfile.java
@@ -71,6 +71,53 @@
*/
public static final int TYPE_APPLICATION = 2;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = false, prefix = "ERROR_", value = {
+ ERROR_UNKNOWN,
+ ERROR_NO_PERMISSION,
+ ERROR_DUPLICATE,
+ ERROR_INVALID_ARGUMENT,
+ ERROR_NOT_ALLOWLISTED
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * Error code for unknown errors.
+ * @hide
+ */
+ public static final int ERROR_UNKNOWN = 0;
+
+ /**
+ * Error code for missing necessary permission to handle the profiles.
+ * @hide
+ */
+ public static final int ERROR_NO_PERMISSION = 1;
+
+ /**
+ * Error code for creating a profile with existing profile type and name.
+ *
+ * @see #getProfileType()
+ * @see #getName()
+ * @hide
+ */
+ public static final int ERROR_DUPLICATE = 2;
+
+ /**
+ * Error code for invalid argument.
+ * @hide
+ */
+ public static final int ERROR_INVALID_ARGUMENT = 3;
+
+ /**
+ * Error code for the case when an operation requires an allowlist but the caller is not in the
+ * list.
+ *
+ * @see MediaQualityManager#getPictureProfileAllowList()
+ * @hide
+ */
+ public static final int ERROR_NOT_ALLOWLISTED = 4;
+
private PictureProfile(@NonNull Parcel in) {
mId = in.readString();
diff --git a/packages/SettingsLib/IntroPreference/Android.bp b/packages/SettingsLib/IntroPreference/Android.bp
index 155db18..8f9fb7a 100644
--- a/packages/SettingsLib/IntroPreference/Android.bp
+++ b/packages/SettingsLib/IntroPreference/Android.bp
@@ -29,5 +29,6 @@
min_sdk_version: "21",
apex_available: [
"//apex_available:platform",
+ "com.android.healthfitness",
],
}
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
index 155ee83..78e27fe 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
@@ -29,6 +29,7 @@
"//apex_available:platform",
"com.android.permission",
"com.android.mediaprovider",
+ "com.android.healthfitness",
],
}
@@ -51,5 +52,6 @@
"//apex_available:platform",
"com.android.permission",
"com.android.mediaprovider",
+ "com.android.healthfitness",
],
}
diff --git a/packages/Shell/src/com/android/shell/BugreportProgressService.java b/packages/Shell/src/com/android/shell/BugreportProgressService.java
index b4d81d6..7c478ac 100644
--- a/packages/Shell/src/com/android/shell/BugreportProgressService.java
+++ b/packages/Shell/src/com/android/shell/BugreportProgressService.java
@@ -353,7 +353,7 @@
public void onDestroy() {
mServiceHandler.getLooper().quit();
mScreenshotHandler.getLooper().quit();
- mBugreportSingleThreadExecutor.close();
+ mBugreportSingleThreadExecutor.shutdown();
super.onDestroy();
}
diff --git a/packages/SystemUI/README.md b/packages/SystemUI/README.md
index 2910bba..635a97e 100644
--- a/packages/SystemUI/README.md
+++ b/packages/SystemUI/README.md
@@ -87,7 +87,7 @@
There are a few places where CommandQueue is used as a bus to communicate
across sysui. Such as when StatusBar calls CommandQueue#recomputeDisableFlags.
-This is generally used a shortcut to directly trigger CommandQueue rather than
+This is generally used as a shortcut to directly trigger CommandQueue rather than
calling StatusManager and waiting for the call to come back to IStatusBar.
### [com.android.systemui.util.NotificationChannels](/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java)
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 02b7667..207ed71 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -16,6 +16,16 @@
}
flag {
+ name: "multiuser_wifi_picker_tracker_support"
+ namespace: "systemui"
+ description: "Adds WifiPickerTracker support for multiple users to support when HSUM is enabled."
+ bug: "371586248"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
+flag {
name: "udfps_view_performance"
namespace: "systemui"
description: "Decrease screen off blocking calls by waiting until the device is finished going to sleep before adding the udfps view."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
index eeab232..163f4b3 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/statusbar/phone/SystemUIDialogFactoryExt.kt
@@ -59,6 +59,7 @@
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.hideFromAccessibility
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
@@ -286,7 +287,10 @@
Surface(
modifier =
Modifier.padding(top = 16.dp, bottom = 6.dp)
- .semantics { contentDescription = dragHandleContentDescription }
+ .semantics {
+ contentDescription = dragHandleContentDescription
+ hideFromAccessibility()
+ }
.clickable { dialog.dismiss() },
color = MaterialTheme.colorScheme.onSurfaceVariant,
shape = MaterialTheme.shapes.extraLarge,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index e7b66c5..d976e8e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -693,8 +693,8 @@
val fromState = updateStateInContent(transition.fromContent)
val toState = updateStateInContent(transition.toContent)
- reconcileStates(element, previousTransition)
- reconcileStates(element, transition)
+ val previousUniqueState = reconcileStates(element, previousTransition, previousState = null)
+ reconcileStates(element, transition, previousState = previousUniqueState)
// Remove the interruption values to all contents but the content(s) where the element will be
// placed, to make sure that interruption deltas are computed only right after this interruption
@@ -721,12 +721,32 @@
/**
* Reconcile the state of [element] in the formContent and toContent of [transition] so that the
* values before interruption have their expected values, taking shared transitions into account.
+ *
+ * @return the unique state this element had during [transition], `null` if it had multiple
+ * different states (i.e. the shared animation was disabled).
*/
-private fun reconcileStates(element: Element, transition: TransitionState.Transition) {
- val fromContentState = element.stateByContent[transition.fromContent] ?: return
- val toContentState = element.stateByContent[transition.toContent] ?: return
+private fun reconcileStates(
+ element: Element,
+ transition: TransitionState.Transition,
+ previousState: Element.State?,
+): Element.State? {
+ fun reconcileWithPreviousState(state: Element.State) {
+ if (previousState != null && state.offsetBeforeInterruption == Offset.Unspecified) {
+ state.updateValuesBeforeInterruption(previousState)
+ }
+ }
+
+ val fromContentState = element.stateByContent[transition.fromContent]
+ val toContentState = element.stateByContent[transition.toContent]
+
+ if (fromContentState == null || toContentState == null) {
+ return (fromContentState ?: toContentState)
+ ?.also { reconcileWithPreviousState(it) }
+ ?.takeIf { it.offsetBeforeInterruption != Offset.Unspecified }
+ }
+
if (!isSharedElementEnabled(element.key, transition)) {
- return
+ return null
}
if (
@@ -735,13 +755,19 @@
) {
// Element is shared and placed in fromContent only.
toContentState.updateValuesBeforeInterruption(fromContentState)
- } else if (
+ return fromContentState
+ }
+
+ if (
toContentState.offsetBeforeInterruption != Offset.Unspecified &&
fromContentState.offsetBeforeInterruption == Offset.Unspecified
) {
// Element is shared and placed in toContent only.
fromContentState.updateValuesBeforeInterruption(toContentState)
+ return toContentState
}
+
+ return null
}
private fun Element.State.selfUpdateValuesBeforeInterruption() {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index 4a90515..a301856 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -2638,4 +2638,58 @@
assertWithMessage("Frame $i didn't replace Foo").that(numberOfPlacements).isEqualTo(0)
}
}
+
+ @Test
+ fun interruption_considerPreviousUniqueState() {
+ @Composable
+ fun SceneScope.Foo(modifier: Modifier = Modifier) {
+ Box(modifier.element(TestElements.Foo).size(50.dp))
+ }
+
+ val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) }
+ val scope =
+ rule.setContentAndCreateMainScope {
+ SceneTransitionLayout(state) {
+ scene(SceneA) { Box(Modifier.fillMaxSize()) { Foo() } }
+ scene(SceneB) { Box(Modifier.fillMaxSize()) }
+ scene(SceneC) {
+ Box(Modifier.fillMaxSize()) { Foo(Modifier.offset(x = 100.dp, y = 100.dp)) }
+ }
+ }
+ }
+
+ // During A => B, Foo disappears and stays in its original position.
+ scope.launch { state.startTransition(transition(SceneA, SceneB)) }
+ rule
+ .onNode(isElement(TestElements.Foo))
+ .assertSizeIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+ // Interrupt A => B by B => C.
+ var interruptionProgress by mutableFloatStateOf(1f)
+ scope.launch {
+ state.startTransition(
+ transition(SceneB, SceneC, interruptionProgress = { interruptionProgress })
+ )
+ }
+
+ // During B => C, Foo appears again. It is still at (0, 0) when the interruption progress is
+ // 100%, and converges to its position (100, 100) in C.
+ rule
+ .onNode(isElement(TestElements.Foo))
+ .assertSizeIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(0.dp, 0.dp)
+
+ interruptionProgress = 0.5f
+ rule
+ .onNode(isElement(TestElements.Foo))
+ .assertSizeIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(50.dp, 50.dp)
+
+ interruptionProgress = 0f
+ rule
+ .onNode(isElement(TestElements.Foo))
+ .assertSizeIsEqualTo(50.dp)
+ .assertPositionInRootIsEqualTo(100.dp, 100.dp)
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
index 160865d..81d3f72 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt
@@ -31,11 +31,8 @@
import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository
import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.fakeTrustRepository
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.AuthenticationFlags
-import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest
@@ -243,16 +240,11 @@
}
@Test
- fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleepInAod() =
+ fun deviceUnlockStatus_becomesUnlocked_whenFingerprintUnlocked_whileDeviceAsleep() =
testScope.runTest {
val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
- kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.AOD,
- testScope = this,
- )
kosmos.powerInteractor.setAsleepForTest()
runCurrent()
@@ -266,26 +258,6 @@
}
@Test
- fun deviceUnlockStatus_staysLocked_whenFingerprintUnlocked_whileDeviceAsleep() =
- testScope.runTest {
- val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus)
- assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
- assertThat(kosmos.keyguardTransitionInteractor.getCurrentState())
- .isEqualTo(KeyguardState.LOCKSCREEN)
-
- kosmos.powerInteractor.setAsleepForTest()
- runCurrent()
-
- assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
-
- kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus(
- SuccessFingerprintAuthenticationStatus(0, true)
- )
- runCurrent()
- assertThat(deviceUnlockStatus?.isUnlocked).isFalse()
- }
-
- @Test
fun deviceEntryRestrictionReason_whenFaceOrFingerprintOrTrust_alwaysNull() =
testScope.runTest {
kosmos.fakeBiometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(false)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
index dda9cd5..4dcbdfa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryTest.kt
@@ -104,67 +104,6 @@
}
}
- @Test
- fun showLabels_updatesFromSharedPreferences() =
- with(kosmos) {
- testScope.runTest {
- val latest by collectLastValue(underTest.showLabels)
- assertThat(latest).isFalse()
-
- setShowLabelsInSharedPreferences(true)
- assertThat(latest).isTrue()
-
- setShowLabelsInSharedPreferences(false)
- assertThat(latest).isFalse()
- }
- }
-
- @Test
- fun showLabels_updatesFromUserChange() =
- with(kosmos) {
- testScope.runTest {
- fakeUserRepository.setUserInfos(USERS)
- val latest by collectLastValue(underTest.showLabels)
-
- fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
- setShowLabelsInSharedPreferences(false)
-
- fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
- setShowLabelsInSharedPreferences(true)
-
- fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
- assertThat(latest).isFalse()
- }
- }
-
- @Test
- fun setShowLabels_inSharedPreferences() {
- underTest.setShowLabels(false)
- assertThat(getShowLabelsFromSharedPreferences(true)).isFalse()
-
- underTest.setShowLabels(true)
- assertThat(getShowLabelsFromSharedPreferences(false)).isTrue()
- }
-
- @Test
- fun setShowLabels_forDifferentUser() =
- with(kosmos) {
- testScope.runTest {
- fakeUserRepository.setUserInfos(USERS)
-
- fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
- underTest.setShowLabels(false)
- assertThat(getShowLabelsFromSharedPreferences(true)).isFalse()
-
- fakeUserRepository.setSelectedUserInfo(ANOTHER_USER)
- underTest.setShowLabels(true)
- assertThat(getShowLabelsFromSharedPreferences(false)).isTrue()
-
- fakeUserRepository.setSelectedUserInfo(PRIMARY_USER)
- assertThat(getShowLabelsFromSharedPreferences(true)).isFalse()
- }
- }
-
private fun getSharedPreferences(): SharedPreferences =
with(kosmos) {
return userFileManager.getSharedPreferences(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
index 55f88cc..08b9961 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt
@@ -753,7 +753,7 @@
lastSleepReason = WakeSleepReason.POWER_BUTTON,
powerButtonLaunchGestureTriggered = false,
)
- transitionStateFlow.value = Transition(from = Scenes.Shade, to = Scenes.Lockscreen)
+ transitionStateFlow.value = Transition(from = Scenes.Gone, to = Scenes.Lockscreen)
assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen)
kosmos.fakePowerRepository.updateWakefulness(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt
new file mode 100644
index 0000000..a9a5cac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/data/repository/ShadePositionRepositoryTest.kt
@@ -0,0 +1,81 @@
+/*
+ * 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.data.repository
+
+import android.view.Display
+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.kosmos.testScope
+import com.android.systemui.statusbar.commandline.commandRegistry
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ShadePositionRepositoryTest : SysuiTestCase() {
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+ private val commandRegistry = kosmos.commandRegistry
+ private val pw = PrintWriter(StringWriter())
+
+ private val underTest = ShadePositionRepositoryImpl(commandRegistry)
+
+ @Before
+ fun setUp() {
+ underTest.start()
+ }
+
+ @Test
+ fun commandDisplayOverride_updatesDisplayId() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+ assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+ val newDisplayId = 2
+ commandRegistry.onShellCommand(
+ pw,
+ arrayOf("shade_display_override", newDisplayId.toString()),
+ )
+
+ assertThat(displayId).isEqualTo(newDisplayId)
+ }
+
+ @Test
+ fun commandShadeDisplayOverride_resetsDisplayId() =
+ testScope.runTest {
+ val displayId by collectLastValue(underTest.displayId)
+ assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+
+ val newDisplayId = 2
+ commandRegistry.onShellCommand(
+ pw,
+ arrayOf("shade_display_override", newDisplayId.toString()),
+ )
+ assertThat(displayId).isEqualTo(newDisplayId)
+
+ commandRegistry.onShellCommand(pw, arrayOf("shade_display_override", "reset"))
+ assertThat(displayId).isEqualTo(Display.DEFAULT_DISPLAY)
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
index 643acdb..2a3878c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt
@@ -16,18 +16,22 @@
package com.android.systemui.statusbar.connectivity
+import android.content.Context
+import android.os.UserHandle
import android.os.UserManager
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
+import android.platform.test.annotations.EnableFlags
import android.testing.TestableLooper.RunWithLooper
import androidx.lifecycle.Lifecycle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.UserTracker
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.capture
import com.android.wifitrackerlib.WifiEntry
import com.android.wifitrackerlib.WifiPickerTracker
import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -35,36 +39,28 @@
import org.mockito.ArgumentMatchers.anyList
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
class AccessPointControllerImplTest : SysuiTestCase() {
- @Mock
- private lateinit var userManager: UserManager
- @Mock
- private lateinit var userTracker: UserTracker
- @Mock
- private lateinit var wifiPickerTrackerFactory:
- WifiPickerTrackerFactory
- @Mock
- private lateinit var wifiPickerTracker: WifiPickerTracker
- @Mock
- private lateinit var callback: AccessPointController.AccessPointCallback
- @Mock
- private lateinit var otherCallback: AccessPointController.AccessPointCallback
- @Mock
- private lateinit var wifiEntryConnected: WifiEntry
- @Mock
- private lateinit var wifiEntryOther: WifiEntry
- @Captor
- private lateinit var wifiEntryListCaptor: ArgumentCaptor<List<WifiEntry>>
+ @Mock private lateinit var userManager: UserManager
+ @Mock private lateinit var userTracker: UserTracker
+ @Mock private lateinit var wifiPickerTrackerFactory: WifiPickerTrackerFactory
+ @Mock private lateinit var wifiPickerTracker: WifiPickerTracker
+ @Mock private lateinit var callback: AccessPointController.AccessPointCallback
+ @Mock private lateinit var otherCallback: AccessPointController.AccessPointCallback
+ @Mock private lateinit var wifiEntryConnected: WifiEntry
+ @Mock private lateinit var wifiEntryOther: WifiEntry
+ @Captor private lateinit var wifiEntryListCaptor: ArgumentCaptor<List<WifiEntry>>
private val instantExecutor = Executor { it.run() }
private lateinit var controller: AccessPointControllerImpl
@@ -72,19 +68,21 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- `when`(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(wifiPickerTracker)
+ `when`(wifiPickerTrackerFactory.create(any(), any(), any(), any()))
+ .thenReturn(wifiPickerTracker)
`when`(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntryConnected)
- `when`(wifiPickerTracker.wifiEntries).thenReturn(ArrayList<WifiEntry>().apply {
- add(wifiEntryOther)
- })
+ `when`(wifiPickerTracker.wifiEntries)
+ .thenReturn(ArrayList<WifiEntry>().apply { add(wifiEntryOther) })
- controller = AccessPointControllerImpl(
+ controller =
+ AccessPointControllerImpl(
+ mContext,
userManager,
userTracker,
instantExecutor,
- wifiPickerTrackerFactory
- )
+ wifiPickerTrackerFactory,
+ )
controller.init()
}
@@ -183,13 +181,15 @@
@Test
fun testReturnEmptyListWhenNoWifiPickerTracker() {
- `when`(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(null)
- val otherController = AccessPointControllerImpl(
+ `when`(wifiPickerTrackerFactory.create(any(), any(), any(), any())).thenReturn(null)
+ val otherController =
+ AccessPointControllerImpl(
+ mContext,
userManager,
userTracker,
instantExecutor,
- wifiPickerTrackerFactory
- )
+ wifiPickerTrackerFactory,
+ )
otherController.init()
otherController.addAccessPointCallback(callback)
@@ -244,4 +244,19 @@
verify(wifiEntryOther).connect(any())
verify(callback, never()).onSettingsActivityTriggered(any())
}
+
+ @Test
+ @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+ fun switchUsers() {
+ val primaryUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(UserHandle.of(PRIMARY_USER_ID), primaryUserMockContext)
+ controller.onUserSwitched(PRIMARY_USER_ID)
+ // Create is expected to be called once when the test starts and a second time when the user
+ // is switched.
+ verify(wifiPickerTrackerFactory, times(2)).create(any(), any(), any(), any())
+ }
+
+ private companion object {
+ private const val PRIMARY_USER_ID = 1
+ }
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
index dae5542..50db9f7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt
@@ -21,16 +21,19 @@
import android.view.View.VISIBLE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
+import com.android.systemui.res.R
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
@@ -52,8 +55,11 @@
private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
@Mock private lateinit var mediaDataManager: MediaDataManager
@Mock private lateinit var stackLayout: NotificationStackScrollLayout
+ @Mock private lateinit var seenNotificationsInteractor: SeenNotificationsInteractor
private val testableResources = mContext.orCreateTestableResources
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
private lateinit var sizeCalculator: NotificationStackSizeCalculator
@@ -72,7 +78,9 @@
lockscreenShadeTransitionController = lockscreenShadeTransitionController,
mediaDataManager = mediaDataManager,
testableResources.resources,
- ResourcesSplitShadeStateController()
+ ResourcesSplitShadeStateController(),
+ seenNotificationsInteractor = seenNotificationsInteractor,
+ scope = testScope,
)
}
@@ -85,7 +93,7 @@
rows,
spaceForNotifications = 0f,
spaceForShelf = 0f,
- shelfHeight = 0f
+ shelfHeight = 0f,
)
assertThat(maxNotifications).isEqualTo(0)
@@ -101,7 +109,7 @@
rows,
spaceForNotifications = Float.MAX_VALUE,
spaceForShelf = Float.MAX_VALUE,
- shelfHeight
+ shelfHeight,
)
assertThat(maxNotifications).isEqualTo(numberOfRows)
@@ -137,7 +145,7 @@
listOf(row),
/* spaceForNotifications= */ 5f,
/* spaceForShelf= */ 0f,
- /* shelfHeight= */ 0f
+ /* shelfHeight= */ 0f,
)
assertThat(maxNotifications).isEqualTo(1)
@@ -148,11 +156,7 @@
setGapHeight(gapHeight)
val shelfHeight = shelfHeight + dividerHeight
val spaceForNotifications =
- listOf(
- rowHeight + dividerHeight,
- gapHeight + rowHeight + dividerHeight,
- )
- .sum()
+ listOf(rowHeight + dividerHeight, gapHeight + rowHeight + dividerHeight).sum()
val spaceForShelf = gapHeight + dividerHeight + shelfHeight
val rows =
listOf(createMockRow(rowHeight), createMockRow(rowHeight), createMockRow(rowHeight))
@@ -162,7 +166,7 @@
rows,
spaceForNotifications + 1,
spaceForShelf,
- shelfHeight
+ shelfHeight,
)
assertThat(maxNotifications).isEqualTo(2)
@@ -173,12 +177,7 @@
// Each row in separate section.
setGapHeight(gapHeight)
- val notifSpace =
- listOf(
- rowHeight,
- dividerHeight + gapHeight + rowHeight,
- )
- .sum()
+ val notifSpace = listOf(rowHeight, dividerHeight + gapHeight + rowHeight).sum()
val shelfSpace = dividerHeight + gapHeight + shelfHeight
val spaceUsed = notifSpace + shelfSpace
@@ -209,7 +208,7 @@
rows,
spaceForNotifications + 1,
spaceForShelf,
- shelfHeight
+ shelfHeight,
)
assertThat(maxNotifications).isEqualTo(1)
@@ -252,7 +251,7 @@
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = true
+ onLockscreen = true,
)
assertThat(space.whenEnoughSpace).isEqualTo(10f)
}
@@ -272,7 +271,7 @@
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = true
+ onLockscreen = true,
)
assertThat(space.whenEnoughSpace).isEqualTo(5)
}
@@ -291,7 +290,7 @@
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = true
+ onLockscreen = true,
)
assertThat(space.whenSavingSpace).isEqualTo(5)
}
@@ -311,7 +310,7 @@
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = true
+ onLockscreen = true,
)
assertThat(space.whenSavingSpace).isEqualTo(5)
}
@@ -330,7 +329,7 @@
visibleIndex = 0,
previousView = null,
stack = stackLayout,
- onLockscreen = false
+ onLockscreen = false,
)
assertThat(space.whenEnoughSpace).isEqualTo(rowHeight)
assertThat(space.whenSavingSpace).isEqualTo(rowHeight)
@@ -340,14 +339,14 @@
rows: List<ExpandableView>,
spaceForNotifications: Float,
spaceForShelf: Float,
- shelfHeight: Float = this.shelfHeight
+ shelfHeight: Float = this.shelfHeight,
): Int {
setupChildren(rows)
return sizeCalculator.computeMaxKeyguardNotifications(
stackLayout,
spaceForNotifications,
spaceForShelf,
- shelfHeight
+ shelfHeight,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
index b5dbc3f..33223ae 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/WifiRepositorySwitcherTest.kt
@@ -29,6 +29,7 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoWifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
+import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.kotlinArgumentCaptor
@@ -71,6 +72,7 @@
private val demoModelFlow = MutableStateFlow<FakeWifiEventModel?>(null)
private val mainExecutor = FakeExecutor(FakeSystemClock())
+ private val userRepository = FakeUserRepository()
private val testDispatcher = UnconfinedTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -82,10 +84,13 @@
// Never start in demo mode
whenever(demoModeController.isInDemoMode).thenReturn(false)
- whenever(wifiPickerTrackerFactory.create(any(), any(), any())).thenReturn(wifiPickerTracker)
+ whenever(wifiPickerTrackerFactory.create(any(), any(), any(), any()))
+ .thenReturn(wifiPickerTracker)
realImpl =
WifiRepositoryImpl(
+ mContext,
+ userRepository,
testScope.backgroundScope,
mainExecutor,
testDispatcher,
@@ -97,11 +102,7 @@
whenever(demoModeWifiDataSource.wifiEvents).thenReturn(demoModelFlow)
- demoImpl =
- DemoWifiRepository(
- demoModeWifiDataSource,
- testScope.backgroundScope,
- )
+ demoImpl = DemoWifiRepository(demoModeWifiDataSource, testScope.backgroundScope)
underTest =
WifiRepositorySwitcher(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
index 9b47ead..8a45930 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/user/data/repository/UserRepositoryImplTest.kt
@@ -30,6 +30,7 @@
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.kosmos.useUnconfinedTestDispatcher
+import com.android.systemui.res.R
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.testKosmos
import com.android.systemui.user.data.model.SelectedUserModel
@@ -74,6 +75,10 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
tracker = FakeUserTracker()
+ context.orCreateTestableResources.addOverride(
+ R.bool.config_userSwitchingMustGoThroughLoginScreen,
+ false,
+ )
}
@Test
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index f825459..a107322 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -36,6 +36,7 @@
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
import com.android.systemui.people.widget.PeopleBackupHelper
+import com.android.systemui.qs.panels.domain.backup.QSPreferencesBackupHelper
import com.android.systemui.res.R
import com.android.systemui.settings.UserFileManagerImpl
@@ -58,9 +59,9 @@
private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY =
"systemui.keyguard.quickaffordance.shared_preferences"
- private const val COMMUNAL_PREFS_BACKUP_KEY =
- "systemui.communal.shared_preferences"
+ private const val COMMUNAL_PREFS_BACKUP_KEY = "systemui.communal.shared_preferences"
private const val COMMUNAL_STATE_BACKUP_KEY = "systemui.communal_state"
+ private const val QS_PREFERENCES_BACKUP_KEY = "systemui.qs.shared_preferences"
val controlsDataLock = Any()
const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
@@ -74,22 +75,20 @@
val keys = PeopleBackupHelper.getFilesToBackup()
addHelper(
PEOPLE_TILES_BACKUP_KEY,
- PeopleBackupHelper(this, userHandle, keys.toTypedArray())
+ PeopleBackupHelper(this, userHandle, keys.toTypedArray()),
)
addHelper(
KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY,
- KeyguardQuickAffordanceBackupHelper(
- context = this,
- userId = userHandle.identifier,
- ),
+ KeyguardQuickAffordanceBackupHelper(context = this, userId = userHandle.identifier),
+ )
+ addHelper(
+ QS_PREFERENCES_BACKUP_KEY,
+ QSPreferencesBackupHelper(context = this, userId = userHandle.identifier),
)
if (communalEnabled()) {
addHelper(
COMMUNAL_PREFS_BACKUP_KEY,
- CommunalPrefsBackupHelper(
- context = this,
- userId = userHandle.identifier,
- )
+ CommunalPrefsBackupHelper(context = this, userId = userHandle.identifier),
)
addHelper(
COMMUNAL_STATE_BACKUP_KEY,
@@ -110,10 +109,7 @@
}
private fun addControlsHelper(userId: Int) {
- val file = UserFileManagerImpl.createFile(
- userId = userId,
- fileName = CONTROLS,
- )
+ val file = UserFileManagerImpl.createFile(userId = userId, fileName = CONTROLS)
// The map in mapOf is guaranteed to be order preserving
val controlsMap = mapOf(file.getPath() to getPPControlsFile(this, userId))
NoOverwriteFileBackupHelper(controlsDataLock, this, controlsMap).also {
@@ -134,6 +130,7 @@
* @property lock a lock to hold while backing up and restoring the files.
* @property context the context of the [BackupAgent]
* @property fileNamesAndPostProcess a map from the filenames to back up and the post processing
+ *
* ```
* actions to take
* ```
@@ -141,7 +138,7 @@
private class NoOverwriteFileBackupHelper(
val lock: Any,
val context: Context,
- val fileNamesAndPostProcess: Map<String, () -> Unit>
+ val fileNamesAndPostProcess: Map<String, () -> Unit>,
) : FileBackupHelper(context, *fileNamesAndPostProcess.keys.toTypedArray()) {
override fun restoreEntity(data: BackupDataInputStream) {
@@ -152,11 +149,12 @@
return
}
synchronized(lock) {
- traceSection("File restore: ${data.key}") {
- super.restoreEntity(data)
- }
- Log.d(TAG, "Finishing restore for ${data.key} for user ${context.userId}. " +
- "Starting postProcess.")
+ traceSection("File restore: ${data.key}") { super.restoreEntity(data) }
+ Log.d(
+ TAG,
+ "Finishing restore for ${data.key} for user ${context.userId}. " +
+ "Starting postProcess.",
+ )
traceSection("Postprocess: ${data.key}") {
fileNamesAndPostProcess.get(data.key)?.invoke()
}
@@ -167,7 +165,7 @@
override fun performBackup(
oldState: ParcelFileDescriptor?,
data: BackupDataOutput?,
- newState: ParcelFileDescriptor?
+ newState: ParcelFileDescriptor?,
) {
synchronized(lock) { super.performBackup(oldState, data, newState) }
}
@@ -176,15 +174,13 @@
private fun getPPControlsFile(context: Context, userId: Int): () -> Unit {
return {
- val file = UserFileManagerImpl.createFile(
- userId = userId,
- fileName = BackupHelper.CONTROLS,
- )
+ val file = UserFileManagerImpl.createFile(userId = userId, fileName = BackupHelper.CONTROLS)
if (file.exists()) {
- val dest = UserFileManagerImpl.createFile(
- userId = userId,
- fileName = AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME,
- )
+ val dest =
+ UserFileManagerImpl.createFile(
+ userId = userId,
+ fileName = AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME,
+ )
file.copyTo(dest)
val jobScheduler = context.getSystemService(JobScheduler::class.java)
jobScheduler?.schedule(
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index c464a66..6c335e7 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -18,13 +18,18 @@
import com.android.keyguard.EmptyLockIconViewController
import com.android.keyguard.LockIconViewController
+import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule
+import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor
import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition
+import dagger.Binds
import dagger.Lazy
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
import dagger.multibindings.Multibinds
@Module(includes = [DeviceEntryRepositoryModule::class, FaceWakeUpTriggersConfigModule::class])
@@ -34,6 +39,13 @@
*/
@Multibinds abstract fun deviceEntryIconTransitionSet(): Set<DeviceEntryIconTransition>
+ @Binds
+ @IntoMap
+ @ClassKey(DeviceUnlockedInteractor.Activator::class)
+ abstract fun deviceUnlockedInteractorActivator(
+ activator: DeviceUnlockedInteractor.Activator
+ ): CoreStartable
+
companion object {
@Provides
@SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
index 3f937bb..675f00a 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt
@@ -5,6 +5,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.deviceentry.shared.model.DeviceUnlockStatus
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.repository.UserRepository
import dagger.Binds
@@ -42,6 +43,8 @@
*/
val isBypassEnabled: StateFlow<Boolean>
+ val deviceUnlockStatus: MutableStateFlow<DeviceUnlockStatus>
+
/**
* Whether the lockscreen is enabled for the current user. This is `true` whenever the user has
* chosen any secure authentication method and even if they set the lockscreen to be dismissed
@@ -84,6 +87,9 @@
initialValue = keyguardBypassController.bypassEnabled,
)
+ override val deviceUnlockStatus =
+ MutableStateFlow(DeviceUnlockStatus(isUnlocked = false, deviceUnlockSource = null))
+
override suspend fun isLockscreenEnabled(): Boolean {
return withContext(backgroundDispatcher) {
val selectedUserId = userRepository.getSelectedUserInfo().id
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
index 5259c5d..24278ec 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt
@@ -16,7 +16,9 @@
package com.android.systemui.deviceentry.domain.interactor
+import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.systemui.CoreStartable
import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.dagger.SysUISingleton
@@ -26,42 +28,40 @@
import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource
import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import com.android.systemui.flags.SystemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.TrustInteractor
-import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.lifecycle.ExclusiveActivatable
import com.android.systemui.power.domain.interactor.PowerInteractor
-import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class DeviceUnlockedInteractor
@Inject
constructor(
- @Application private val applicationScope: CoroutineScope,
- authenticationInteractor: AuthenticationInteractor,
- deviceEntryRepository: DeviceEntryRepository,
+ private val authenticationInteractor: AuthenticationInteractor,
+ private val repository: DeviceEntryRepository,
trustInteractor: TrustInteractor,
faceAuthInteractor: DeviceEntryFaceAuthInteractor,
fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor,
private val powerInteractor: PowerInteractor,
private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor,
private val systemPropertiesHelper: SystemPropertiesHelper,
- keyguardTransitionInteractor: KeyguardTransitionInteractor,
-) {
+) : ExclusiveActivatable() {
private val deviceUnlockSource =
merge(
@@ -69,7 +69,7 @@
faceAuthInteractor.isAuthenticated
.filter { it }
.map {
- if (deviceEntryRepository.isBypassEnabled.value) {
+ if (repository.isBypassEnabled.value) {
DeviceUnlockSource.FaceWithBypass
} else {
DeviceUnlockSource.FaceWithoutBypass
@@ -163,43 +163,59 @@
* proceed.
*/
val deviceUnlockStatus: StateFlow<DeviceUnlockStatus> =
- authenticationInteractor.authenticationMethod
- .flatMapLatest { authMethod ->
- if (!authMethod.isSecure) {
- flowOf(DeviceUnlockStatus(true, null))
- } else if (authMethod == AuthenticationMethodModel.Sim) {
- // Device is locked if SIM is locked.
- flowOf(DeviceUnlockStatus(false, null))
- } else {
- combine(
- powerInteractor.isAsleep,
- isInLockdown,
- keyguardTransitionInteractor
- .transitionValue(KeyguardState.AOD)
- .map { it == 1f }
- .distinctUntilChanged(),
- ::Triple,
- )
- .flatMapLatestConflated { (isAsleep, isInLockdown, isAod) ->
- val isForceLocked =
- when {
- isAsleep && !isAod -> true
- isInLockdown -> true
- else -> false
- }
- if (isForceLocked) {
- flowOf(DeviceUnlockStatus(false, null))
- } else {
- deviceUnlockSource.map { DeviceUnlockStatus(true, it) }
+ repository.deviceUnlockStatus.asStateFlow()
+
+ override suspend fun onActivated(): Nothing {
+ authenticationInteractor.authenticationMethod.collectLatest { authMethod ->
+ if (!authMethod.isSecure) {
+ // Device remains unlocked as long as the authentication method is not secure.
+ Log.d(TAG, "remaining unlocked because auth method not secure")
+ repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null)
+ } else if (authMethod == AuthenticationMethodModel.Sim) {
+ // Device remains locked while SIM is locked.
+ Log.d(TAG, "remaining locked because SIM locked")
+ repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null)
+ } else {
+ try {
+ Log.d(TAG, "started watching for lock and unlock events")
+ coroutineScope {
+ launch {
+ // Unlock the device when a new unlock source is detected.
+ deviceUnlockSource.collect {
+ Log.d(TAG, "unlocking due to \"$it\"")
+ repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, it)
}
}
+
+ launch {
+ // Lock events.
+ merge(
+ // Device goes to sleep.
+ powerInteractor.isAsleep
+ .distinctUntilChanged()
+ .filter { it }
+ .map { "asleep" },
+ // Device enters lockdown.
+ isInLockdown
+ .distinctUntilChanged()
+ .filter { it }
+ .map { "lockdown" },
+ )
+ .collect { reason: String ->
+ Log.d(TAG, "locking due to \"$reason\"")
+ repository.deviceUnlockStatus.value =
+ DeviceUnlockStatus(false, null)
+ }
+ }
+ }
+ } finally {
+ Log.d(TAG, "stopped watching for lock and unlock events")
}
}
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.Eagerly,
- initialValue = DeviceUnlockStatus(false, null),
- )
+ }
+
+ awaitCancellation()
+ }
private fun DeviceEntryRestrictionReason?.isInLockdown(): Boolean {
return when (this) {
@@ -226,7 +242,20 @@
return systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE
}
+ /** [CoreStartable] that activates the [DeviceUnlockedInteractor]. */
+ class Activator
+ @Inject
+ constructor(
+ @Application private val applicationScope: CoroutineScope,
+ private val interactor: DeviceUnlockedInteractor,
+ ) : CoreStartable {
+ override fun start() {
+ applicationScope.launch { interactor.activate() }
+ }
+ }
+
companion object {
+ private val TAG = "DeviceUnlockedInteractor"
@VisibleForTesting const val SYS_BOOT_REASON_PROP = "sys.boot.reason.last"
@VisibleForTesting const val REBOOT_MAINLINE_UPDATE = "reboot,mainline_update"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
index 971598d..b0c6073 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepository.kt
@@ -17,9 +17,16 @@
package com.android.systemui.qs.panels.data.repository
import android.content.Context
+import android.content.IntentFilter
import android.content.SharedPreferences
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.backup.BackupHelper.Companion.ACTION_RESTORE_FINISHED
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.qs.panels.shared.model.PanelsLog
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.settings.UserFileManager
import com.android.systemui.user.data.repository.UserRepository
@@ -29,9 +36,11 @@
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
/** Repository for QS user preferences. */
@OptIn(ExperimentalCoroutinesApi::class)
@@ -43,26 +52,31 @@
private val userRepository: UserRepository,
private val defaultLargeTilesRepository: DefaultLargeTilesRepository,
@Background private val backgroundDispatcher: CoroutineDispatcher,
+ @PanelsLog private val logBuffer: LogBuffer,
+ broadcastDispatcher: BroadcastDispatcher,
) {
- /** Whether to show the labels on icon tiles for the current user. */
- val showLabels: Flow<Boolean> =
- userRepository.selectedUserInfo
- .flatMapLatest { userInfo ->
- val prefs = getSharedPrefs(userInfo.id)
- prefs.observe().emitOnStart().map { prefs.getBoolean(ICON_LABELS_KEY, false) }
- }
- .flowOn(backgroundDispatcher)
+ private val logger by lazy { Logger(logBuffer, TAG) }
+
+ private val backupRestorationEvents: Flow<Unit> =
+ broadcastDispatcher
+ .broadcastFlow(
+ filter = IntentFilter(ACTION_RESTORE_FINISHED),
+ flags = Context.RECEIVER_NOT_EXPORTED,
+ permission = BackupHelper.PERMISSION_SELF,
+ )
+ .onEach { logger.i("Restored state for QS preferences.") }
+ .emitOnStart()
/** Set of [TileSpec] to display as large tiles for the current user. */
val largeTilesSpecs: Flow<Set<TileSpec>> =
- userRepository.selectedUserInfo
- .flatMapLatest { userInfo ->
+ combine(backupRestorationEvents, userRepository.selectedUserInfo, ::Pair)
+ .flatMapLatest { (_, userInfo) ->
val prefs = getSharedPrefs(userInfo.id)
prefs.observe().emitOnStart().map {
prefs
.getStringSet(
LARGE_TILES_SPECS_KEY,
- defaultLargeTilesRepository.defaultLargeTiles.map { it.spec }.toSet()
+ defaultLargeTilesRepository.defaultLargeTiles.map { it.spec }.toSet(),
)
?.map { TileSpec.create(it) }
?.toSet() ?: defaultLargeTilesRepository.defaultLargeTiles
@@ -70,13 +84,6 @@
}
.flowOn(backgroundDispatcher)
- /** Sets for the current user whether to show the labels on icon tiles. */
- fun setShowLabels(showLabels: Boolean) {
- with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
- edit().putBoolean(ICON_LABELS_KEY, showLabels).apply()
- }
- }
-
/** Sets for the current user the set of [TileSpec] to display as large tiles. */
fun setLargeTilesSpecs(specs: Set<TileSpec>) {
with(getSharedPrefs(userRepository.getSelectedUserInfo().id)) {
@@ -85,15 +92,11 @@
}
private fun getSharedPrefs(userId: Int): SharedPreferences {
- return userFileManager.getSharedPreferences(
- FILE_NAME,
- Context.MODE_PRIVATE,
- userId,
- )
+ return userFileManager.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE, userId)
}
companion object {
- private const val ICON_LABELS_KEY = "show_icon_labels"
+ private const val TAG = "QSPreferencesRepository"
private const val LARGE_TILES_SPECS_KEY = "large_tiles_specs"
const val FILE_NAME = "quick_settings_prefs"
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/backup/QSPreferencesBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/backup/QSPreferencesBackupHelper.kt
new file mode 100644
index 0000000..2c51ca9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/backup/QSPreferencesBackupHelper.kt
@@ -0,0 +1,28 @@
+/*
+ * 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.domain.backup
+
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import com.android.systemui.qs.panels.data.repository.QSPreferencesRepository.Companion.FILE_NAME
+import com.android.systemui.settings.UserFileManagerImpl
+
+class QSPreferencesBackupHelper(context: Context, userId: Int) :
+ SharedPreferencesBackupHelper(
+ context,
+ UserFileManagerImpl.createFile(userId = userId, fileName = FILE_NAME).path,
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
index 42d4eff..63510b8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeDisplayAwareModule.kt
@@ -20,6 +20,7 @@
import android.content.res.Resources
import android.view.LayoutInflater
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+import com.android.systemui.CoreStartable
import com.android.systemui.common.ui.ConfigurationState
import com.android.systemui.common.ui.ConfigurationStateImpl
import com.android.systemui.common.ui.GlobalConfig
@@ -29,12 +30,16 @@
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.res.R
+import com.android.systemui.shade.data.repository.ShadePositionRepository
+import com.android.systemui.shade.data.repository.ShadePositionRepositoryImpl
import com.android.systemui.shade.shared.flag.ShadeWindowGoesAround
import com.android.systemui.statusbar.phone.ConfigurationControllerImpl
import com.android.systemui.statusbar.phone.ConfigurationForwarder
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.Module
import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
/**
* Module responsible for managing display-specific components and resources for the notification
@@ -149,4 +154,25 @@
configurationInteractor
}
}
+
+ @SysUISingleton
+ @Provides
+ @ShadeDisplayAware
+ fun provideShadePositionRepository(impl: ShadePositionRepositoryImpl): ShadePositionRepository {
+ ShadeWindowGoesAround.isUnexpectedlyInLegacyMode()
+ return impl
+ }
+
+ @Provides
+ @IntoMap
+ @ClassKey(ShadePositionRepositoryImpl::class)
+ fun provideShadePositionRepositoryAsCoreStartable(
+ impl: ShadePositionRepositoryImpl
+ ): CoreStartable {
+ return if (ShadeWindowGoesAround.isEnabled) {
+ impl
+ } else {
+ CoreStartable.NOP
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
new file mode 100644
index 0000000..802fc0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadePrimaryDisplayCommand.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.view.Display
+import com.android.systemui.shade.data.repository.ShadePositionRepository
+import com.android.systemui.statusbar.commandline.Command
+import java.io.PrintWriter
+
+class ShadePrimaryDisplayCommand(private val positionRepository: ShadePositionRepository) :
+ Command {
+
+ override fun execute(pw: PrintWriter, args: List<String>) {
+ if (args[0].lowercase() == "reset") {
+ positionRepository.resetDisplayId()
+ pw.println("Reset shade primary display id to ${Display.DEFAULT_DISPLAY}")
+ return
+ }
+
+ val displayId: Int =
+ try {
+ args[0].toInt()
+ } catch (e: NumberFormatException) {
+ pw.println("Error: task id should be an integer")
+ return
+ }
+
+ if (displayId < 0) {
+ pw.println("Error: display id should be positive integer")
+ }
+
+ positionRepository.setDisplayId(displayId)
+ pw.println("New shade primary display id is $displayId")
+ }
+
+ override fun help(pw: PrintWriter) {
+ pw.println("shade_display_override <displayId> ")
+ pw.println("Set the display which is holding the shade.")
+ pw.println("shade_display_override reset ")
+ pw.println("Reset the display which is holding the shade.")
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt
new file mode 100644
index 0000000..24c067a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadePositionRepository.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.data.repository
+
+import android.view.Display
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadePrimaryDisplayCommand
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import javax.inject.Inject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+interface ShadePositionRepository {
+ /** ID of the display which currently hosts the shade */
+ val displayId: StateFlow<Int>
+
+ /**
+ * Updates the value of the shade display id stored, emitting to the new display id to every
+ * component dependent on the shade display id
+ */
+ fun setDisplayId(displayId: Int)
+
+ /** Resets value of shade primary display to the default display */
+ fun resetDisplayId()
+}
+
+/** Source of truth for the display currently holding the shade. */
+@SysUISingleton
+class ShadePositionRepositoryImpl
+@Inject
+constructor(private val commandRegistry: CommandRegistry) : ShadePositionRepository, CoreStartable {
+ private val _displayId = MutableStateFlow(Display.DEFAULT_DISPLAY)
+
+ override val displayId: StateFlow<Int>
+ get() = _displayId
+
+ override fun setDisplayId(displayId: Int) {
+ _displayId.value = displayId
+ }
+
+ override fun resetDisplayId() {
+ _displayId.value = Display.DEFAULT_DISPLAY
+ }
+
+ override fun start() {
+ commandRegistry.registerCommand("shade_display_override") {
+ ShadePrimaryDisplayCommand(this)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
index 3a31851..52a79d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImpl.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.connectivity;
+import static com.android.systemui.Flags.multiuserWifiPickerTrackerSupport;
+
+import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.os.UserManager;
@@ -65,13 +68,16 @@
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
private int mCurrentUser;
+ private Context mContext;
public AccessPointControllerImpl(
+ Context context,
UserManager userManager,
UserTracker userTracker,
Executor mainExecutor,
WifiPickerTrackerFactory wifiPickerTrackerFactory
) {
+ mContext = context;
mUserManager = userManager;
mUserTracker = userTracker;
mCurrentUser = userTracker.getUserId();
@@ -87,7 +93,11 @@
*/
public void init() {
if (mWifiPickerTracker == null) {
- mWifiPickerTracker = mWifiPickerTrackerFactory.create(this.getLifecycle(), this, TAG);
+ // We are creating the WifiPickerTracker during init to make sure we have one
+ // available at all times however we expect this to be recreated very quickly
+ // with a user-specific context in onUserSwitched.
+ mWifiPickerTracker =
+ mWifiPickerTrackerFactory.create(mContext, this.getLifecycle(), this, TAG);
}
}
@@ -116,6 +126,19 @@
void onUserSwitched(int newUserId) {
mCurrentUser = newUserId;
+ // Return early if multiuser support is not enabled.
+ if (!multiuserWifiPickerTrackerSupport()) {
+ return;
+ }
+
+ if (mWifiPickerTracker != null) {
+ mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.CREATED));
+ }
+ Context context = mContext.createContextAsUser(UserHandle.of(newUserId), /* flags= */ 0);
+ mWifiPickerTracker = mWifiPickerTrackerFactory.create(context, mLifecycle, this, TAG);
+ if (!mCallbacks.isEmpty()) {
+ mMainExecutor.execute(() -> mLifecycle.setCurrentState(Lifecycle.State.STARTED));
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
index dc2ebe5..947ce33 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiPickerTrackerFactory.kt
@@ -22,6 +22,7 @@
import android.os.Handler
import android.os.SimpleClock
import androidx.lifecycle.Lifecycle
+import com.android.systemui.Flags.multiuserWifiPickerTrackerSupport
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.util.concurrency.ThreadFactory
@@ -41,7 +42,7 @@
class WifiPickerTrackerFactory
@Inject
constructor(
- private val context: Context,
+ private val applicationContext: Context,
private val wifiManager: WifiManager?,
private val connectivityManager: ConnectivityManager,
private val systemClock: SystemClock,
@@ -64,16 +65,23 @@
* @return a new [WifiPickerTracker] or null if [WifiManager] is null.
*/
fun create(
+ userContext: Context,
lifecycle: Lifecycle,
listener: WifiPickerTrackerCallback,
name: String,
): WifiPickerTracker? {
return if (wifiManager == null) {
null
- } else
+ } else {
+ val contextToUse =
+ if (multiuserWifiPickerTrackerSupport()) {
+ userContext
+ } else {
+ applicationContext
+ }
WifiPickerTracker(
lifecycle,
- context,
+ contextToUse,
wifiManager,
connectivityManager,
mainHandler,
@@ -86,6 +94,7 @@
SCAN_INTERVAL_MILLIS,
listener,
)
+ }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
index 2fded34..e232849 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/LockScreenMinimalismCoordinator.kt
@@ -19,6 +19,7 @@
import android.annotation.SuppressLint
import android.app.NotificationManager
import androidx.annotation.VisibleForTesting
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.Dumpable
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dump.DumpManager
@@ -50,7 +51,6 @@
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import com.android.app.tracing.coroutines.launchTraced as launch
/**
* If the setting is enabled, this will track seen notifications and ensure that they only show in
@@ -74,7 +74,7 @@
private val unseenNotifications = mutableSetOf<NotificationEntry>()
private var isShadeVisible = false
- private var unseenFilterEnabled = false
+ private var minimalismEnabled = false
override fun attach(pipeline: NotifPipeline) {
if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) {
@@ -83,7 +83,7 @@
pipeline.addPromoter(unseenNotifPromoter)
pipeline.addOnBeforeTransformGroupsListener(::pickOutTopUnseenNotifs)
pipeline.addCollectionListener(collectionListener)
- scope.launch { trackUnseenFilterSettingChanges() }
+ scope.launch { trackLockScreenNotificationMinimalismSettingChanges() }
dumpManager.registerDumpable(this)
}
@@ -136,12 +136,12 @@
return seenNotificationsInteractor.isLockScreenNotificationMinimalismEnabled()
}
- private suspend fun trackUnseenFilterSettingChanges() {
+ private suspend fun trackLockScreenNotificationMinimalismSettingChanges() {
// Only filter the seen notifs when the lock screen minimalism feature settings is on.
minimalismFeatureSettingEnabled().collectLatest { isMinimalismSettingEnabled ->
// update local field and invalidate if necessary
- if (isMinimalismSettingEnabled != unseenFilterEnabled) {
- unseenFilterEnabled = isMinimalismSettingEnabled
+ if (isMinimalismSettingEnabled != minimalismEnabled) {
+ minimalismEnabled = isMinimalismSettingEnabled
unseenNotifications.clear()
unseenNotifPromoter.invalidateList("unseen setting changed")
}
@@ -156,21 +156,21 @@
private val collectionListener =
object : NotifCollectionListener {
override fun onEntryAdded(entry: NotificationEntry) {
- if (unseenFilterEnabled && !isShadeVisible) {
+ if (minimalismEnabled && !isShadeVisible) {
logger.logUnseenAdded(entry.key)
unseenNotifications.add(entry)
}
}
override fun onEntryUpdated(entry: NotificationEntry) {
- if (unseenFilterEnabled && !isShadeVisible) {
+ if (minimalismEnabled && !isShadeVisible) {
logger.logUnseenUpdated(entry.key)
unseenNotifications.add(entry)
}
}
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
- if (unseenFilterEnabled && unseenNotifications.remove(entry)) {
+ if (minimalismEnabled && unseenNotifications.remove(entry)) {
logger.logUnseenRemoved(entry.key)
}
}
@@ -178,7 +178,7 @@
private fun pickOutTopUnseenNotifs(list: List<ListEntry>) {
if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return
- if (!unseenFilterEnabled) return
+ if (!minimalismEnabled) return
// Only ever elevate a top unseen notification on keyguard, not even locked shade
if (statusBarStateController.state != StatusBarState.KEYGUARD) {
seenNotificationsInteractor.setTopOngoingNotification(null)
@@ -215,6 +215,7 @@
override fun shouldPromoteToTopLevel(child: NotificationEntry): Boolean =
when {
NotificationMinimalism.isUnexpectedlyInLegacyMode() -> false
+ !minimalismEnabled -> false
seenNotificationsInteractor.isTopOngoingNotification(child) -> true
!NotificationMinimalism.ungroupTopUnseen -> false
else -> seenNotificationsInteractor.isTopUnseenNotification(child)
@@ -225,6 +226,7 @@
object : NotifSectioner("TopOngoing", BUCKET_TOP_ONGOING) {
override fun isInSection(entry: ListEntry): Boolean {
if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return false
+ if (!minimalismEnabled) return false
return entry.anyEntry { notificationEntry ->
seenNotificationsInteractor.isTopOngoingNotification(notificationEntry)
}
@@ -235,6 +237,7 @@
object : NotifSectioner("TopUnseen", BUCKET_TOP_UNSEEN) {
override fun isInSection(entry: ListEntry): Boolean {
if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return false
+ if (!minimalismEnabled) return false
return entry.anyEntry { notificationEntry ->
seenNotificationsInteractor.isTopUnseenNotification(notificationEntry)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
index 3bc5495..5dff812 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt
@@ -21,12 +21,14 @@
import android.view.View.GONE
import androidx.annotation.VisibleForTesting
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.res.R
import com.android.systemui.statusbar.LockscreenShadeTransitionController
import com.android.systemui.statusbar.StatusBarState.KEYGUARD
import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.domain.interactor.SeenNotificationsInteractor
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
import com.android.systemui.statusbar.notification.shared.NotificationMinimalism
@@ -38,6 +40,9 @@
import kotlin.math.max
import kotlin.math.min
import kotlin.properties.Delegates.notNull
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
private const val TAG = "NotifStackSizeCalc"
private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
@@ -56,7 +61,9 @@
private val lockscreenShadeTransitionController: LockscreenShadeTransitionController,
private val mediaDataManager: MediaDataManager,
@Main private val resources: Resources,
- private val splitShadeStateController: SplitShadeStateController
+ private val splitShadeStateController: SplitShadeStateController,
+ private val seenNotificationsInteractor: SeenNotificationsInteractor,
+ @Application private val scope: CoroutineScope,
) {
/**
@@ -74,7 +81,7 @@
/** Whether we allow keyguard to show less important notifications above the shelf. */
private val limitLockScreenToOneImportant
- get() = NotificationMinimalism.isEnabled
+ get() = NotificationMinimalism.isEnabled && minimalismSettingEnabled
/** Minimum space between two notifications, see [calculateGapAndDividerHeight]. */
private var dividerHeight by notNull<Float>()
@@ -85,8 +92,14 @@
*/
private var saveSpaceOnLockscreen = false
+ /** True when the lock screen notification minimalism feature setting is enabled */
+ private var minimalismSettingEnabled = false
+
init {
updateResources()
+ if (NotificationMinimalism.isEnabled) {
+ scope.launch { trackLockScreenNotificationMinimalismSettingChanges() }
+ }
}
private fun allowedByPolicy(stackHeight: StackHeight): Boolean =
@@ -199,7 +212,7 @@
canStackFitInSpace(
heightResult,
notifSpace = notifSpace,
- shelfSpace = shelfSpace
+ shelfSpace = shelfSpace,
) == FitResult.FIT
}
@@ -229,7 +242,7 @@
canStackFitInSpace(
heightResult,
notifSpace = notifSpace,
- shelfSpace = shelfSpace
+ shelfSpace = shelfSpace,
) != FitResult.NO_FIT
}
log { "\t--- maxNotifications=$maxNotifications" }
@@ -277,7 +290,7 @@
fun computeHeight(
stack: NotificationStackScrollLayout,
maxNotifs: Int,
- shelfHeight: Float
+ shelfHeight: Float,
): Float {
log { "\n" }
log { "computeHeight ---" }
@@ -311,7 +324,7 @@
private enum class FitResult {
FIT,
FIT_IF_SAVE_SPACE,
- NO_FIT
+ NO_FIT,
}
data class SpaceNeeded(
@@ -319,7 +332,7 @@
val whenEnoughSpace: Float,
// Float height of space needed when showing collapsed layout for FSI HUNs.
- val whenSavingSpace: Float
+ val whenSavingSpace: Float,
)
private data class StackHeight(
@@ -335,9 +348,19 @@
val shelfHeightWithSpaceBefore: Float,
/** Whether the stack should actually be forced into the shelf before this height. */
- val shouldForceIntoShelf: Boolean
+ val shouldForceIntoShelf: Boolean,
)
+ private suspend fun trackLockScreenNotificationMinimalismSettingChanges() {
+ if (NotificationMinimalism.isUnexpectedlyInLegacyMode()) return
+ seenNotificationsInteractor.isLockScreenNotificationMinimalismEnabled().collectLatest {
+ if (it != minimalismSettingEnabled) {
+ minimalismSettingEnabled = it
+ }
+ Log.i(TAG, "minimalismSettingEnabled: $minimalismSettingEnabled")
+ }
+ }
+
private fun computeHeightPerNotificationLimit(
stack: NotificationStackScrollLayout,
shelfHeight: Float,
@@ -377,7 +400,7 @@
stack,
previous = currentNotification,
current = children[firstViewInShelfIndex],
- currentIndex = firstViewInShelfIndex
+ currentIndex = firstViewInShelfIndex,
)
spaceBeforeShelf + shelfHeight
}
@@ -390,14 +413,15 @@
log {
"\tcomputeHeightPerNotificationLimit i=$i notifs=$notifications " +
"notifsHeightSavingSpace=$notifsWithCollapsedHun" +
- " shelfWithSpaceBefore=$shelfWithSpaceBefore"
+ " shelfWithSpaceBefore=$shelfWithSpaceBefore" +
+ " limitLockScreenToOneImportant: $limitLockScreenToOneImportant"
}
yield(
StackHeight(
notifsHeight = notifications,
notifsHeightSavingSpace = notifsWithCollapsedHun,
shelfHeightWithSpaceBefore = shelfWithSpaceBefore,
- shouldForceIntoShelf = counter?.shouldForceIntoShelf() ?: false
+ shouldForceIntoShelf = counter?.shouldForceIntoShelf() ?: false,
)
)
}
@@ -462,6 +486,10 @@
fun dump(pw: PrintWriter, args: Array<out String>) {
pw.println("NotificationStackSizeCalculator saveSpaceOnLockscreen=$saveSpaceOnLockscreen")
+ pw.println(
+ "NotificationStackSizeCalculator " +
+ "limitLockScreenToOneImportant=$limitLockScreenToOneImportant"
+ )
}
private fun ExpandableView.isShowable(onLockscreen: Boolean): Boolean {
@@ -484,7 +512,7 @@
stack: NotificationStackScrollLayout,
previous: ExpandableView?,
current: ExpandableView?,
- currentIndex: Int
+ currentIndex: Int,
): Float {
if (currentIndex == 0) {
return 0f
@@ -536,11 +564,7 @@
takeWhile(predicate).count() - 1
/** Counts the number of notifications for each type of bucket */
- data class BucketTypeCounter(
- var ongoing: Int = 0,
- var important: Int = 0,
- var other: Int = 0,
- ) {
+ data class BucketTypeCounter(var ongoing: Int = 0, var important: Int = 0, var other: Int = 0) {
fun incrementForBucket(@PriorityBucket bucket: Int?) {
when (bucket) {
BUCKET_MEDIA_CONTROLS,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
index 76024cd..c7b6be3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImpl.kt
@@ -17,12 +17,15 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
import android.annotation.SuppressLint
+import android.content.Context
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
+import android.os.UserHandle
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Flags.multiuserWifiPickerTrackerSupport
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
@@ -43,6 +46,7 @@
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel.Unavailable.toHotspotDeviceType
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
+import com.android.systemui.user.data.repository.UserRepository
import com.android.wifitrackerlib.HotspotNetworkEntry
import com.android.wifitrackerlib.MergedCarrierEntry
import com.android.wifitrackerlib.WifiEntry
@@ -53,10 +57,12 @@
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
@@ -68,6 +74,8 @@
class WifiRepositoryImpl
@Inject
constructor(
+ @Application applicationContext: Context,
+ private val userRepository: UserRepository,
@Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
@Background private val bgDispatcher: CoroutineDispatcher,
@@ -84,90 +92,226 @@
private var wifiPickerTracker: WifiPickerTracker? = null
- private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> = run {
- var current =
- WifiPickerTrackerInfo(
- state = WIFI_STATE_DEFAULT,
- isDefault = false,
- primaryNetwork = WIFI_NETWORK_DEFAULT,
- secondaryNetworks = emptyList(),
- )
- callbackFlow {
- val callback =
- object : WifiPickerTracker.WifiPickerTrackerCallback {
- override fun onWifiEntriesChanged() {
- val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
- logOnWifiEntriesChanged(connectedEntry)
+ @VisibleForTesting
+ val selectedUserContext: Flow<Context> =
+ userRepository.selectedUserInfo.map {
+ applicationContext.createContextAsUser(UserHandle.of(it.id), /* flags= */ 0)
+ }
- val activeNetworks = wifiPickerTracker?.activeWifiEntries ?: emptyList()
- val secondaryNetworks =
- activeNetworks
- .filter { it != connectedEntry && !it.isPrimaryNetwork }
- .map { it.toWifiNetworkModel() }
+ var current =
+ WifiPickerTrackerInfo(
+ state = WIFI_STATE_DEFAULT,
+ isDefault = false,
+ primaryNetwork = WIFI_NETWORK_DEFAULT,
+ secondaryNetworks = emptyList(),
+ )
- // [WifiPickerTracker.connectedWifiEntry] will return the same instance
- // but with updated internals. For example, when its validation status
- // changes from false to true, the same instance is re-used but with the
- // validated field updated.
- //
- // Because it's the same instance, the flow won't re-emit the value
- // (even though the internals have changed). So, we need to transform it
- // into our internal model immediately. [toWifiNetworkModel] always
- // returns a new instance, so the flow is guaranteed to emit.
- send(
- newPrimaryNetwork =
- connectedEntry?.toPrimaryWifiNetworkModel()
- ?: WIFI_NETWORK_DEFAULT,
- newSecondaryNetworks = secondaryNetworks,
- newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
- )
+ @kotlinx.coroutines.ExperimentalCoroutinesApi
+ private val wifiPickerTrackerInfo: StateFlow<WifiPickerTrackerInfo> =
+ if (multiuserWifiPickerTrackerSupport()) {
+ selectedUserContext
+ .flatMapLatest { currentContext
+ -> // flatMapLatest because when selectedUserContext emits a new value, we want
+ // to
+ // re-create a whole flow
+ callbackFlow {
+ val callback =
+ object : WifiPickerTracker.WifiPickerTrackerCallback {
+ override fun onWifiEntriesChanged() {
+ val connectedEntry =
+ wifiPickerTracker.mergedOrPrimaryConnection
+ logOnWifiEntriesChanged(connectedEntry)
+
+ val activeNetworks =
+ wifiPickerTracker?.activeWifiEntries ?: emptyList()
+ val secondaryNetworks =
+ activeNetworks
+ .filter {
+ it != connectedEntry && !it.isPrimaryNetwork
+ }
+ .map { it.toWifiNetworkModel() }
+
+ // [WifiPickerTracker.connectedWifiEntry] will return the
+ // same
+ // instance but with updated internals. For example, when
+ // its
+ // validation status changes from false to true, the same
+ // instance is re-used but with the validated field updated.
+ //
+ // Because it's the same instance, the flow won't re-emit
+ // the
+ // value (even though the internals have changed). So, we
+ // need
+ // to transform it into our internal model immediately.
+ // [toWifiNetworkModel] always returns a new instance, so
+ // the
+ // flow is guaranteed to emit.
+ send(
+ newPrimaryNetwork =
+ connectedEntry?.toPrimaryWifiNetworkModel()
+ ?: WIFI_NETWORK_DEFAULT,
+ newSecondaryNetworks = secondaryNetworks,
+ newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
+ )
+ }
+
+ override fun onWifiStateChanged() {
+ val state = wifiPickerTracker?.wifiState
+ logOnWifiStateChanged(state)
+ send(newState = state ?: WIFI_STATE_DEFAULT)
+ }
+
+ override fun onNumSavedNetworksChanged() {}
+
+ override fun onNumSavedSubscriptionsChanged() {}
+
+ private fun send(
+ newState: Int = current.state,
+ newIsDefault: Boolean = current.isDefault,
+ newPrimaryNetwork: WifiNetworkModel =
+ current.primaryNetwork,
+ newSecondaryNetworks: List<WifiNetworkModel> =
+ current.secondaryNetworks,
+ ) {
+ val new =
+ WifiPickerTrackerInfo(
+ newState,
+ newIsDefault,
+ newPrimaryNetwork,
+ newSecondaryNetworks,
+ )
+ current = new
+ trySend(new)
+ }
+ }
+ wifiPickerTracker =
+ wifiPickerTrackerFactory
+ .create(currentContext, lifecycle, callback, "WifiRepository")
+ .apply {
+ // By default, [WifiPickerTracker] will scan to see all
+ // available wifi networks in the area. Because SysUI only
+ // needs to display the **connected** network, we don't
+ // need scans to be running (and in fact, running scans is
+ // costly and should be avoided whenever possible).
+ this?.disableScanning()
+ }
+
+ // The lifecycle must be STARTED in order for the callback to receive
+ // events.
+ mainExecutor.execute {
+ lifecycle.currentState = Lifecycle.State.STARTED
+ }
+ awaitClose {
+ mainExecutor.execute {
+ lifecycle.currentState = Lifecycle.State.CREATED
+ }
+ }
}
-
- override fun onWifiStateChanged() {
- val state = wifiPickerTracker?.wifiState
- logOnWifiStateChanged(state)
- send(newState = state ?: WIFI_STATE_DEFAULT)
- }
-
- override fun onNumSavedNetworksChanged() {}
-
- override fun onNumSavedSubscriptionsChanged() {}
-
- private fun send(
- newState: Int = current.state,
- newIsDefault: Boolean = current.isDefault,
- newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
- newSecondaryNetworks: List<WifiNetworkModel> =
- current.secondaryNetworks,
- ) {
- val new =
- WifiPickerTrackerInfo(
- newState,
- newIsDefault,
- newPrimaryNetwork,
- newSecondaryNetworks,
- )
- current = new
- trySend(new)
- }
- }
-
- wifiPickerTracker =
- wifiPickerTrackerFactory.create(lifecycle, callback, "WifiRepository").apply {
- // By default, [WifiPickerTracker] will scan to see all available wifi
- // networks in the area. Because SysUI only needs to display the
- // **connected** network, we don't need scans to be running (and in fact,
- // running scans is costly and should be avoided whenever possible).
- this?.disableScanning()
- }
- // The lifecycle must be STARTED in order for the callback to receive events.
- mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
- awaitClose {
- mainExecutor.execute { lifecycle.currentState = Lifecycle.State.CREATED }
+ .stateIn(scope, SharingStarted.Eagerly, current)
}
+ .stateIn(scope, SharingStarted.Eagerly, current)
+ } else {
+
+ run {
+ var current =
+ WifiPickerTrackerInfo(
+ state = WIFI_STATE_DEFAULT,
+ isDefault = false,
+ primaryNetwork = WIFI_NETWORK_DEFAULT,
+ secondaryNetworks = emptyList(),
+ )
+ callbackFlow {
+ val callback =
+ object : WifiPickerTracker.WifiPickerTrackerCallback {
+ override fun onWifiEntriesChanged() {
+ val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
+ logOnWifiEntriesChanged(connectedEntry)
+
+ val activeNetworks =
+ wifiPickerTracker?.activeWifiEntries ?: emptyList()
+ val secondaryNetworks =
+ activeNetworks
+ .filter { it != connectedEntry && !it.isPrimaryNetwork }
+ .map { it.toWifiNetworkModel() }
+
+ // [WifiPickerTracker.connectedWifiEntry] will return the same
+ // instance
+ // but with updated internals. For example, when its validation
+ // status
+ // changes from false to true, the same instance is re-used but
+ // with the
+ // validated field updated.
+ //
+ // Because it's the same instance, the flow won't re-emit the
+ // value
+ // (even though the internals have changed). So, we need to
+ // transform it
+ // into our internal model immediately. [toWifiNetworkModel]
+ // always
+ // returns a new instance, so the flow is guaranteed to emit.
+ send(
+ newPrimaryNetwork =
+ connectedEntry?.toPrimaryWifiNetworkModel()
+ ?: WIFI_NETWORK_DEFAULT,
+ newSecondaryNetworks = secondaryNetworks,
+ newIsDefault = connectedEntry?.isDefaultNetwork ?: false,
+ )
+ }
+
+ override fun onWifiStateChanged() {
+ val state = wifiPickerTracker?.wifiState
+ logOnWifiStateChanged(state)
+ send(newState = state ?: WIFI_STATE_DEFAULT)
+ }
+
+ override fun onNumSavedNetworksChanged() {}
+
+ override fun onNumSavedSubscriptionsChanged() {}
+
+ private fun send(
+ newState: Int = current.state,
+ newIsDefault: Boolean = current.isDefault,
+ newPrimaryNetwork: WifiNetworkModel = current.primaryNetwork,
+ newSecondaryNetworks: List<WifiNetworkModel> =
+ current.secondaryNetworks,
+ ) {
+ val new =
+ WifiPickerTrackerInfo(
+ newState,
+ newIsDefault,
+ newPrimaryNetwork,
+ newSecondaryNetworks,
+ )
+ current = new
+ trySend(new)
+ }
+ }
+
+ wifiPickerTracker =
+ wifiPickerTrackerFactory
+ .create(applicationContext, lifecycle, callback, "WifiRepository")
+ .apply {
+ // By default, [WifiPickerTracker] will scan to see all
+ // available wifi
+ // networks in the area. Because SysUI only needs to display the
+ // **connected** network, we don't need scans to be running (and
+ // in fact,
+ // running scans is costly and should be avoided whenever
+ // possible).
+ this?.disableScanning()
+ }
+ // The lifecycle must be STARTED in order for the callback to receive
+ // events.
+ mainExecutor.execute { lifecycle.currentState = Lifecycle.State.STARTED }
+ awaitClose {
+ mainExecutor.execute {
+ lifecycle.currentState = Lifecycle.State.CREATED
+ }
+ }
+ }
+ .stateIn(scope, SharingStarted.Eagerly, current)
}
- .stateIn(scope, SharingStarted.Eagerly, current)
- }
+ }
override val isWifiEnabled: StateFlow<Boolean> =
wifiPickerTrackerInfo
@@ -185,11 +329,7 @@
wifiPickerTrackerInfo
.map { it.primaryNetwork }
.distinctUntilChanged()
- .logDiffsForTable(
- tableLogger,
- columnPrefix = "",
- initialValue = WIFI_NETWORK_DEFAULT,
- )
+ .logDiffsForTable(tableLogger, columnPrefix = "", initialValue = WIFI_NETWORK_DEFAULT)
.stateIn(scope, SharingStarted.Eagerly, WIFI_NETWORK_DEFAULT)
override val secondaryNetworks: StateFlow<List<WifiNetworkModel>> =
@@ -348,7 +488,7 @@
TAG,
LogLevel.DEBUG,
{ str1 = prettyPrintActivity(activity) },
- { "onActivityChanged: $str1" }
+ { "onActivityChanged: $str1" },
)
}
@@ -379,13 +519,15 @@
/** The currently primary wifi network. */
val primaryNetwork: WifiNetworkModel,
/** The current secondary network(s), if any. Specifically excludes the primary network. */
- val secondaryNetworks: List<WifiNetworkModel>
+ val secondaryNetworks: List<WifiNetworkModel>,
)
@SysUISingleton
class Factory
@Inject
constructor(
+ @Application private val applicationContext: Context,
+ private val userRepository: UserRepository,
@Application private val scope: CoroutineScope,
@Main private val mainExecutor: Executor,
@Background private val bgDispatcher: CoroutineDispatcher,
@@ -395,6 +537,8 @@
) {
fun create(wifiManager: WifiManager): WifiRepositoryImpl {
return WifiRepositoryImpl(
+ applicationContext,
+ userRepository,
scope,
mainExecutor,
bgDispatcher,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
index c7bd5a1..9187e3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/StatusBarPolicyModule.java
@@ -206,12 +206,14 @@
@SysUISingleton
@Provides
static AccessPointControllerImpl provideAccessPointControllerImpl(
+ @Application Context context,
UserManager userManager,
UserTracker userTracker,
@Main Executor mainExecutor,
WifiPickerTrackerFactory wifiPickerTrackerFactory
) {
AccessPointControllerImpl controller = new AccessPointControllerImpl(
+ context,
userManager,
userTracker,
mainExecutor,
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index e9a33e0..ad97b21 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -348,42 +348,53 @@
private suspend fun getSettings(): UserSwitcherSettingsModel {
return withContext(backgroundDispatcher) {
- val isSimpleUserSwitcher =
- globalSettings.getInt(
- SETTING_SIMPLE_USER_SWITCHER,
- if (
- appContext.resources.getBoolean(
- com.android.internal.R.bool.config_expandLockScreenUserSwitcher
- )
- ) {
- 1
- } else {
- 0
- },
- ) != 0
+ if (
+ // TODO(b/378068979): remove once login screen-specific logic
+ // is implemented at framework level.
+ appContext.resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen)
+ ) {
+ UserSwitcherSettingsModel(
+ isSimpleUserSwitcher = false,
+ isAddUsersFromLockscreen = false,
+ isUserSwitcherEnabled = false,
+ )
+ } else {
+ val isSimpleUserSwitcher =
+ globalSettings.getInt(
+ SETTING_SIMPLE_USER_SWITCHER,
+ if (
+ appContext.resources.getBoolean(
+ com.android.internal.R.bool.config_expandLockScreenUserSwitcher
+ )
+ ) {
+ 1
+ } else {
+ 0
+ },
+ ) != 0
- val isAddUsersFromLockscreen =
- globalSettings.getInt(Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0
+ val isAddUsersFromLockscreen =
+ globalSettings.getInt(Settings.Global.ADD_USERS_WHEN_LOCKED, 0) != 0
- val isUserSwitcherEnabled =
- globalSettings.getInt(
- Settings.Global.USER_SWITCHER_ENABLED,
- if (
- appContext.resources.getBoolean(
- com.android.internal.R.bool.config_showUserSwitcherByDefault
- )
- ) {
- 1
- } else {
- 0
- },
- ) != 0
-
- UserSwitcherSettingsModel(
- isSimpleUserSwitcher = isSimpleUserSwitcher,
- isAddUsersFromLockscreen = isAddUsersFromLockscreen,
- isUserSwitcherEnabled = isUserSwitcherEnabled,
- )
+ val isUserSwitcherEnabled =
+ globalSettings.getInt(
+ Settings.Global.USER_SWITCHER_ENABLED,
+ if (
+ appContext.resources.getBoolean(
+ com.android.internal.R.bool.config_showUserSwitcherByDefault
+ )
+ ) {
+ 1
+ } else {
+ 0
+ },
+ ) != 0
+ UserSwitcherSettingsModel(
+ isSimpleUserSwitcher = isSimpleUserSwitcher,
+ isAddUsersFromLockscreen = isAddUsersFromLockscreen,
+ isUserSwitcherEnabled = isUserSwitcherEnabled,
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
index a798360..bcbd679 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserSwitcherRepository.kt
@@ -21,6 +21,7 @@
import android.os.Handler
import android.os.UserManager
import android.provider.Settings.Global.USER_SWITCHER_ENABLED
+import com.android.app.tracing.coroutines.launchTraced as launch
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -40,7 +41,6 @@
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
-import com.android.app.tracing.coroutines.launchTraced as launch
import kotlinx.coroutines.withContext
interface UserSwitcherRepository {
@@ -67,6 +67,9 @@
private val showUserSwitcherForSingleUser =
context.resources.getBoolean(R.bool.qs_show_user_switcher_for_single_user)
+ private val userSwitchingMustGoThroughLoginScreen =
+ context.resources.getBoolean(R.bool.config_userSwitchingMustGoThroughLoginScreen)
+
override val isEnabled: Flow<Boolean> = conflatedCallbackFlow {
suspend fun updateState() {
trySendWithFailureLogging(isUserSwitcherEnabled(), TAG)
@@ -135,7 +138,13 @@
private suspend fun isUserSwitcherEnabled(): Boolean {
return withContext(bgDispatcher) {
- userManager.isUserSwitcherEnabled(showUserSwitcherForSingleUser)
+ // TODO(b/378068979): remove once login screen-specific logic
+ // is implemented at framework level.
+ if (userSwitchingMustGoThroughLoginScreen) {
+ false
+ } else {
+ userManager.isUserSwitcherEnabled(showUserSwitcherForSingleUser)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index c48898a..2e0b7c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -67,12 +67,12 @@
import com.android.systemui.statusbar.pipeline.wifi.data.repository.WifiRepository
import com.android.systemui.statusbar.pipeline.wifi.data.repository.prod.WifiRepositoryImpl
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
import com.android.systemui.util.time.FakeSystemClock
import com.android.wifitrackerlib.MergedCarrierEntry
import com.android.wifitrackerlib.WifiEntry
@@ -96,6 +96,8 @@
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -137,6 +139,7 @@
private val wifiPickerTrackerCallback =
argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
private val vcnTransportInfo = VcnTransportInfo.Builder().build()
+ private val userRepository = kosmos.fakeUserRepository
private val testDispatcher = StandardTestDispatcher()
private val testScope = TestScope(testDispatcher)
@@ -159,7 +162,14 @@
logcatTableLogBuffer(kosmos, "test")
}
- whenever(wifiPickerTrackerFactory.create(any(), capture(wifiPickerTrackerCallback), any()))
+ whenever(
+ wifiPickerTrackerFactory.create(
+ any(),
+ any(),
+ capture(wifiPickerTrackerCallback),
+ any(),
+ )
+ )
.thenReturn(wifiPickerTracker)
// For convenience, set up the subscription info callbacks
@@ -188,6 +198,8 @@
wifiRepository =
WifiRepositoryImpl(
+ mContext,
+ userRepository,
testScope.backgroundScope,
mainExecutor,
testDispatcher,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
index 44e1437..d823bf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryImplTest.kt
@@ -16,13 +16,19 @@
package com.android.systemui.statusbar.pipeline.wifi.data.repository.prod
+import android.content.Context
+import android.content.pm.UserInfo
import android.net.wifi.ScanResult
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.UNKNOWN_SSID
import android.net.wifi.sharedconnectivity.app.NetworkProviderInfo
+import android.os.UserHandle
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.log.LogBuffer
@@ -33,12 +39,10 @@
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel
import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiScanEntry
import com.android.systemui.testKosmos
+import com.android.systemui.user.data.repository.fakeUserRepository
+import com.android.systemui.user.data.repository.userRepository
import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.fakeSystemClock
import com.android.wifitrackerlib.HotspotNetworkEntry
import com.android.wifitrackerlib.HotspotNetworkEntry.DeviceType
@@ -56,7 +60,12 @@
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
-import org.mockito.Mockito.verify
+import org.mockito.kotlin.any
+import org.mockito.kotlin.capture
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
/**
* Note: Most of these tests are duplicates of [WifiRepositoryImplTest] tests.
@@ -67,8 +76,9 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-class WifiRepositoryImplTest : SysuiTestCase() {
+class WifiRepositoryImplTest() : SysuiTestCase() {
private val kosmos = testKosmos()
+ private val userRepository = kosmos.fakeUserRepository
// Using lazy means that the class will only be constructed once it's fetched. Because the
// repository internally sets some values on construction, we need to set up some test
@@ -76,6 +86,8 @@
// inside each test case without needing to manually recreate the repository.
private val underTest: WifiRepositoryImpl by lazy {
WifiRepositoryImpl(
+ mContext,
+ userRepository,
testScope.backgroundScope,
executor,
dispatcher,
@@ -101,7 +113,8 @@
@Before
fun setUp() {
- whenever(wifiPickerTrackerFactory.create(any(), capture(callbackCaptor), any()))
+ userRepository.setUserInfos(listOf(PRIMARY_USER, ANOTHER_USER))
+ whenever(wifiPickerTrackerFactory.create(any(), any(), capture(callbackCaptor), any()))
.thenReturn(wifiPickerTracker)
}
@@ -1203,6 +1216,95 @@
assertThat(latest).isEmpty()
}
+ // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
+ // this needs to
+ // be updated to capture the argument instead so currentUserContext can be private.
+ @Test
+ @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+ fun oneUserVerifyCreatingWifiPickerTracker_multiuserFlagEnabled() =
+ testScope.runTest {
+ val primaryUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(
+ UserHandle.of(PRIMARY_USER_ID),
+ primaryUserMockContext,
+ )
+
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ runCurrent()
+ val currentUserContext by collectLastValue(underTest.selectedUserContext)
+
+ assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+ verify(wifiPickerTrackerFactory).create(any(), any(), any(), any())
+ }
+
+ // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
+ // this needs to
+ // be updated to capture the argument instead so currentUserContext can be private.
+ @Test
+ @EnableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+ fun changeUserVerifyCreatingWifiPickerTracker_multiuserEnabled() =
+ testScope.runTest {
+ val primaryUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(
+ UserHandle.of(PRIMARY_USER_ID),
+ primaryUserMockContext,
+ )
+
+ runCurrent()
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ runCurrent()
+ val currentUserContext by collectLastValue(underTest.selectedUserContext)
+
+ assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+
+ val otherUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(
+ UserHandle.of(ANOTHER_USER_ID),
+ otherUserMockContext,
+ )
+
+ runCurrent()
+ userRepository.setSelectedUserInfo(ANOTHER_USER)
+ runCurrent()
+ val otherUserContext by collectLastValue(underTest.selectedUserContext)
+
+ assertThat(otherUserContext).isEqualTo(otherUserMockContext)
+ verify(wifiPickerTrackerFactory, times(2)).create(any(), any(), any(), any())
+ }
+
+ // TODO(b/371586248): This test currently require currentUserContext to be public for testing,
+ // this needs to
+ // be updated to capture the argument instead so currentUserContext can be private.
+ @Test
+ @DisableFlags(FLAG_MULTIUSER_WIFI_PICKER_TRACKER_SUPPORT)
+ fun changeUserVerifyCreatingWifiPickerTracker_multiuserDisabled() =
+ testScope.runTest {
+ val primaryUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(
+ UserHandle.of(PRIMARY_USER_ID),
+ primaryUserMockContext,
+ )
+
+ runCurrent()
+ userRepository.setSelectedUserInfo(PRIMARY_USER)
+ runCurrent()
+ val currentUserContext by collectLastValue(underTest.selectedUserContext)
+
+ assertThat(currentUserContext).isEqualTo(primaryUserMockContext)
+
+ val otherUserMockContext = mock<Context>()
+ mContext.prepareCreateContextAsUser(
+ UserHandle.of(ANOTHER_USER_ID),
+ otherUserMockContext,
+ )
+
+ runCurrent()
+ userRepository.setSelectedUserInfo(ANOTHER_USER)
+ runCurrent()
+
+ verify(wifiPickerTrackerFactory, times(1)).create(any(), any(), any(), any())
+ }
+
private fun getCallback(): WifiPickerTracker.WifiPickerTrackerCallback {
testScope.runCurrent()
return callbackCaptor.value
@@ -1231,5 +1333,20 @@
private companion object {
const val TITLE = "AB"
+ private const val PRIMARY_USER_ID = 0
+ private val PRIMARY_USER =
+ UserInfo(
+ /* id= */ PRIMARY_USER_ID,
+ /* name= */ "primary user",
+ /* flags= */ UserInfo.FLAG_PROFILE,
+ )
+
+ private const val ANOTHER_USER_ID = 1
+ private val ANOTHER_USER =
+ UserInfo(
+ /* id= */ ANOTHER_USER_ID,
+ /* name= */ "another user",
+ /* flags= */ UserInfo.FLAG_PROFILE,
+ )
}
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
index 2dcd275..f6ff4c4 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt
@@ -16,6 +16,7 @@
package com.android.systemui.deviceentry.data.repository
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus
import dagger.Binds
import dagger.Module
import javax.inject.Inject
@@ -35,6 +36,9 @@
private var pendingLockscreenEnabled = _isLockscreenEnabled.value
+ override val deviceUnlockStatus =
+ MutableStateFlow(DeviceUnlockStatus(isUnlocked = false, deviceUnlockSource = null))
+
override suspend fun isLockscreenEnabled(): Boolean {
_isLockscreenEnabled.value = pendingLockscreenEnabled
return isLockscreenEnabled.value
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
index 8922b2f..be84e33 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt
@@ -19,25 +19,23 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.deviceentry.data.repository.deviceEntryRepository
import com.android.systemui.flags.fakeSystemPropertiesHelper
-import com.android.systemui.flags.systemPropertiesHelper
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.domain.interactor.trustInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.power.domain.interactor.powerInteractor
val Kosmos.deviceUnlockedInteractor by Fixture {
DeviceUnlockedInteractor(
- applicationScope = applicationCoroutineScope,
- authenticationInteractor = authenticationInteractor,
- deviceEntryRepository = deviceEntryRepository,
- trustInteractor = trustInteractor,
- faceAuthInteractor = deviceEntryFaceAuthInteractor,
- fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
- powerInteractor = powerInteractor,
- biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
- systemPropertiesHelper = fakeSystemPropertiesHelper,
- keyguardTransitionInteractor = keyguardTransitionInteractor,
- )
+ authenticationInteractor = authenticationInteractor,
+ repository = deviceEntryRepository,
+ trustInteractor = trustInteractor,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
+ fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor,
+ powerInteractor = powerInteractor,
+ biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor,
+ systemPropertiesHelper = fakeSystemPropertiesHelper,
+ )
+ .apply { activateIn(testScope) }
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
index 513d4e4..1395b18 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/panels/data/repository/QSPreferencesRepositoryKosmos.kt
@@ -16,8 +16,10 @@
package com.android.systemui.qs.panels.data.repository
+import com.android.systemui.broadcast.broadcastDispatcher
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.settings.userFileManager
import com.android.systemui.user.data.repository.userRepository
@@ -27,6 +29,8 @@
userFileManager,
userRepository,
defaultLargeTilesRepository,
- testDispatcher
+ testDispatcher,
+ FakeLogBuffer.Factory.create(),
+ broadcastDispatcher,
)
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 617cca9..d6fc6e4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -27,6 +27,8 @@
import android.content.Context;
import android.graphics.Region;
import android.hardware.input.InputManager;
+import android.hardware.input.KeyGestureEvent;
+import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.os.SystemClock;
@@ -44,11 +46,14 @@
import android.view.MotionEvent.PointerProperties;
import android.view.accessibility.AccessibilityEvent;
+import androidx.annotation.Nullable;
+
import com.android.server.LocalServices;
import com.android.server.accessibility.gestures.TouchExplorer;
import com.android.server.accessibility.magnification.FullScreenMagnificationController;
import com.android.server.accessibility.magnification.FullScreenMagnificationGestureHandler;
import com.android.server.accessibility.magnification.FullScreenMagnificationVibrationHelper;
+import com.android.server.accessibility.magnification.MagnificationController;
import com.android.server.accessibility.magnification.MagnificationGestureHandler;
import com.android.server.accessibility.magnification.MouseEventHandler;
import com.android.server.accessibility.magnification.WindowMagnificationGestureHandler;
@@ -187,6 +192,8 @@
private final AccessibilityManagerService mAms;
+ private final InputManager mInputManager;
+
private final SparseArray<EventStreamTransformation> mEventHandler;
private final SparseArray<TouchExplorer> mTouchExplorer = new SparseArray<>(0);
@@ -228,6 +235,47 @@
*/
private MotionEvent mLastActiveDeviceMotionEvent = null;
+ private boolean mKeyGestureEventHandlerInstalled = false;
+ private InputManager.KeyGestureEventHandler mKeyGestureEventHandler =
+ new InputManager.KeyGestureEventHandler() {
+ @Override
+ public boolean handleKeyGestureEvent(
+ @NonNull KeyGestureEvent event,
+ @Nullable IBinder focusedToken) {
+ final boolean complete =
+ event.getAction() == KeyGestureEvent.ACTION_GESTURE_COMPLETE
+ && !event.isCancelled();
+ final int gestureType = event.getKeyGestureType();
+ final int displayId = isDisplayIdValid(event.getDisplayId())
+ ? event.getDisplayId() : Display.DEFAULT_DISPLAY;
+
+ switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN:
+ if (complete) {
+ mAms.getMagnificationController().scaleMagnificationByStep(
+ displayId, MagnificationController.ZOOM_DIRECTION_IN);
+ }
+ return true;
+ case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT:
+ if (complete) {
+ mAms.getMagnificationController().scaleMagnificationByStep(
+ displayId, MagnificationController.ZOOM_DIRECTION_OUT);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isKeyGestureSupported(int gestureType) {
+ return switch (gestureType) {
+ case KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT -> true;
+ default -> false;
+ };
+ }
+ };
+
private static MotionEvent cancelMotion(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_CANCEL
|| event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT
@@ -287,6 +335,7 @@
mContext = context;
mAms = service;
mPm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+ mInputManager = context.getSystemService(InputManager.class);
mEventHandler = eventHandler;
}
@@ -723,6 +772,12 @@
createMagnificationGestureHandler(displayId, displayContext);
addFirstEventHandler(displayId, magnificationGestureHandler);
mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
+
+ if (com.android.hardware.input.Flags.enableTalkbackAndMagnifierKeyGestures()
+ && !mKeyGestureEventHandlerInstalled) {
+ mInputManager.registerKeyGestureEventHandler(mKeyGestureEventHandler);
+ mKeyGestureEventHandlerInstalled = true;
+ }
}
if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
@@ -842,6 +897,11 @@
mMouseKeysInterceptor.onDestroy();
mMouseKeysInterceptor = null;
}
+
+ if (mKeyGestureEventHandlerInstalled) {
+ mInputManager.unregisterKeyGestureEventHandler(mKeyGestureEventHandler);
+ mKeyGestureEventHandlerInstalled = false;
+ }
}
private MagnificationGestureHandler createMagnificationGestureHandler(
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index d40e747..51c4305 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -27,6 +27,7 @@
import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
import android.accessibilityservice.MagnificationConfig;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -101,6 +102,7 @@
private int mMagnificationCapabilities = ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
/** Whether the platform supports window magnification feature. */
private final boolean mSupportWindowMagnification;
+ private final MagnificationScaleStepProvider mScaleStepProvider;
private final Executor mBackgroundExecutor;
@@ -131,6 +133,14 @@
.UiChangesForAccessibilityCallbacks> mAccessibilityCallbacksDelegateArray =
new SparseArray<>();
+ // Direction magnifier scale can be altered.
+ public static final int ZOOM_DIRECTION_IN = 0;
+ public static final int ZOOM_DIRECTION_OUT = 1;
+
+ @IntDef({ZOOM_DIRECTION_IN, ZOOM_DIRECTION_OUT})
+ public @interface ZoomDirection {
+ }
+
/**
* A callback to inform the magnification transition result on the given display.
*/
@@ -144,6 +154,41 @@
void onResult(int displayId, boolean success);
}
+
+ /**
+ * An interface to configure how much the magnification scale should be affected when moving in
+ * steps.
+ */
+ public interface MagnificationScaleStepProvider {
+ /**
+ * Calculate the next value given which direction (in/out) to adjust the magnification
+ * scale.
+ *
+ * @param currentScale The current magnification scale value.
+ * @param direction Whether to zoom in or out.
+ * @return The next scale value.
+ */
+ float nextScaleStep(float currentScale, @ZoomDirection int direction);
+ }
+
+ public static class DefaultMagnificationScaleStepProvider implements
+ MagnificationScaleStepProvider {
+ // Factor of magnification scale. For example, when this value is 1.189, scale
+ // value will be changed x1.000, x1.189, x1.414, x1.681, x2.000, ...
+ // Note: this value is 2.0 ^ (1 / 4).
+ public static final float ZOOM_STEP_SCALE_FACTOR = 1.18920712f;
+
+ @Override
+ public float nextScaleStep(float currentScale, @ZoomDirection int direction) {
+ final int stepDelta = direction == ZOOM_DIRECTION_IN ? 1 : -1;
+ final long scaleIndex = Math.round(
+ Math.log(currentScale) / Math.log(ZOOM_STEP_SCALE_FACTOR));
+ final float nextScale = (float) Math.pow(ZOOM_STEP_SCALE_FACTOR,
+ scaleIndex + stepDelta);
+ return MagnificationScaleProvider.constrainScale(nextScale);
+ }
+ }
+
public MagnificationController(AccessibilityManagerService ams, Object lock,
Context context, MagnificationScaleProvider scaleProvider,
Executor backgroundExecutor) {
@@ -156,6 +201,7 @@
.getAccessibilityController().setUiChangesForAccessibilityCallbacks(this);
mSupportWindowMagnification = context.getPackageManager().hasSystemFeature(
FEATURE_WINDOW_MAGNIFICATION);
+ mScaleStepProvider = new DefaultMagnificationScaleStepProvider();
mAlwaysOnMagnificationFeatureFlag = new AlwaysOnMagnificationFeatureFlag(context);
mAlwaysOnMagnificationFeatureFlag.addOnChangedListener(
@@ -891,6 +937,37 @@
return isActivated;
}
+ /**
+ * Scales the magnifier on the given display one step in/out based on the zoomIn param.
+ *
+ * @param displayId The logical display id.
+ * @param direction Whether the scale should be zoomed in or out.
+ * @return {@code true} if the magnification scale was affected.
+ */
+ public boolean scaleMagnificationByStep(int displayId, @ZoomDirection int direction) {
+ if (getFullScreenMagnificationController().isActivated(displayId)) {
+ final float magnificationScale = getFullScreenMagnificationController().getScale(
+ displayId);
+ final float nextMagnificationScale = mScaleStepProvider.nextScaleStep(
+ magnificationScale, direction);
+ getFullScreenMagnificationController().setScaleAndCenter(displayId,
+ nextMagnificationScale,
+ Float.NaN, Float.NaN, true, MAGNIFICATION_GESTURE_HANDLER_ID);
+ return nextMagnificationScale != magnificationScale;
+ }
+
+ if (getMagnificationConnectionManager().isWindowMagnifierEnabled(displayId)) {
+ final float magnificationScale = getMagnificationConnectionManager().getScale(
+ displayId);
+ final float nextMagnificationScale = mScaleStepProvider.nextScaleStep(
+ magnificationScale, direction);
+ getMagnificationConnectionManager().setScale(displayId, nextMagnificationScale);
+ return nextMagnificationScale != magnificationScale;
+ }
+
+ return false;
+ }
+
private final class DisableMagnificationCallback implements
MagnificationAnimationCallback {
private final TransitionCallBack mTransitionCallBack;
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index ddccb37..466d477 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -281,6 +281,9 @@
private static final int SCHEDULE_FILE_VERSION = 1;
public static final String SETTINGS_PACKAGE = "com.android.providers.settings";
+
+ public static final String TELEPHONY_PROVIDER_PACKAGE = "com.android.providers.telephony";
+
public static final String SHARED_BACKUP_AGENT_PACKAGE = "com.android.sharedstoragebackup";
// Pseudoname that we use for the Package Manager metadata "package".
diff --git a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
index f24a3c1..508b62c 100644
--- a/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
+++ b/services/backup/java/com/android/server/backup/utils/BackupEligibilityRules.java
@@ -21,6 +21,7 @@
import static com.android.server.backup.UserBackupManagerService.PACKAGE_MANAGER_SENTINEL;
import static com.android.server.backup.UserBackupManagerService.SETTINGS_PACKAGE;
import static com.android.server.backup.UserBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
+import static com.android.server.backup.UserBackupManagerService.TELEPHONY_PROVIDER_PACKAGE;
import static com.android.server.backup.UserBackupManagerService.WALLPAPER_PACKAGE;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
@@ -75,6 +76,12 @@
systemPackagesAllowedForProfileUser,
Sets.newArraySet(WALLPAPER_PACKAGE, SETTINGS_PACKAGE));
+ static {
+ if (UserManager.isHeadlessSystemUserMode()) {
+ systemPackagesAllowedForNonSystemUsers.add(TELEPHONY_PROVIDER_PACKAGE);
+ }
+ }
+
private final PackageManager mPackageManager;
private final PackageManagerInternal mPackageManagerInternal;
private final int mUserId;
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 7f482ac..aea16b0 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -238,6 +238,7 @@
"connectivity_flags_lib",
"device_config_service_flags_java",
"dreams_flags_lib",
+ "aconfig_flags_java",
"aconfig_new_storage_flags_lib",
"powerstats_flags_lib",
"locksettings_flags_lib",
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 72a9a2d..fa22862 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -88,6 +88,7 @@
import android.telephony.ims.ImsCallSession;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.MediaQualityStatus;
+import android.telephony.satellite.NtnSignalStrength;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -440,6 +441,7 @@
private boolean[] mCarrierRoamingNtnEligible = null;
private List<IntArray> mCarrierRoamingNtnAvailableServices;
+ private NtnSignalStrength[] mCarrierRoamingNtnSignalStrength;
// Local cache to check if Satellite Modem is enabled
private AtomicBoolean mIsSatelliteEnabled;
@@ -745,6 +747,12 @@
mSCBMDuration = copyOf(mSCBMDuration, mNumPhones);
mCarrierRoamingNtnMode = copyOf(mCarrierRoamingNtnMode, mNumPhones);
mCarrierRoamingNtnEligible = copyOf(mCarrierRoamingNtnEligible, mNumPhones);
+ if (mCarrierRoamingNtnSignalStrength != null) {
+ mCarrierRoamingNtnSignalStrength = copyOf(
+ mCarrierRoamingNtnSignalStrength, mNumPhones);
+ } else {
+ mCarrierRoamingNtnSignalStrength = new NtnSignalStrength[mNumPhones];
+ }
// ds -> ss switch.
if (mNumPhones < oldNumPhones) {
cutListToSize(mCellInfo, mNumPhones);
@@ -807,6 +815,8 @@
mCarrierRoamingNtnMode[i] = false;
mCarrierRoamingNtnEligible[i] = false;
mCarrierRoamingNtnAvailableServices.add(i, new IntArray());
+ mCarrierRoamingNtnSignalStrength[i] = new NtnSignalStrength(
+ NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE);
}
}
}
@@ -883,6 +893,7 @@
mCarrierRoamingNtnMode = new boolean[numPhones];
mCarrierRoamingNtnEligible = new boolean[numPhones];
mCarrierRoamingNtnAvailableServices = new ArrayList<>();
+ mCarrierRoamingNtnSignalStrength = new NtnSignalStrength[numPhones];
mIsSatelliteEnabled = new AtomicBoolean();
mWasSatelliteEnabledNotified = new AtomicBoolean();
@@ -932,6 +943,8 @@
mCarrierRoamingNtnMode[i] = false;
mCarrierRoamingNtnEligible[i] = false;
mCarrierRoamingNtnAvailableServices.add(i, new IntArray());
+ mCarrierRoamingNtnSignalStrength[i] = new NtnSignalStrength(
+ NtnSignalStrength.NTN_SIGNAL_STRENGTH_NONE);
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -1565,6 +1578,15 @@
remove(r.binder);
}
}
+ if (events.contains(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED)) {
+ try {
+ r.callback.onCarrierRoamingNtnSignalStrengthChanged(
+ mCarrierRoamingNtnSignalStrength[r.phoneId]);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
}
}
}
@@ -3803,6 +3825,44 @@
}
}
+
+ /**
+ * Notify external listeners that carrier roaming non-terrestrial network
+ * signal strength changed.
+ * @param subId subscription ID.
+ * @param ntnSignalStrength non-terrestrial network signal strength.
+ */
+ public void notifyCarrierRoamingNtnSignalStrengthChanged(int subId,
+ @NonNull NtnSignalStrength ntnSignalStrength) {
+ if (!checkNotifyPermission("notifyCarrierRoamingNtnSignalStrengthChanged")) {
+ log("nnotifyCarrierRoamingNtnSignalStrengthChanged: caller does not have required "
+ + "permissions.");
+ return;
+ }
+
+ if (VDBG) {
+ log("notifyCarrierRoamingNtnSignalStrengthChanged: "
+ + "subId=" + subId + " ntnSignalStrength=" + ntnSignalStrength.getLevel());
+ }
+
+ synchronized (mRecords) {
+ int phoneId = getPhoneIdFromSubId(subId);
+ mCarrierRoamingNtnSignalStrength[phoneId] = ntnSignalStrength;
+ for (Record r : mRecords) {
+ if (r.matchTelephonyCallbackEvent(
+ TelephonyCallback.EVENT_CARRIER_ROAMING_NTN_SIGNAL_STRENGTH_CHANGED)
+ && idMatch(r, subId, phoneId)) {
+ try {
+ r.callback.onCarrierRoamingNtnSignalStrengthChanged(ntnSignalStrength);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
@NeverCompile // Avoid size overhead of debugging code.
@Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -3858,6 +3918,8 @@
pw.println("mSCBMDuration=" + mSCBMDuration[i]);
pw.println("mCarrierRoamingNtnMode=" + mCarrierRoamingNtnMode[i]);
pw.println("mCarrierRoamingNtnEligible=" + mCarrierRoamingNtnEligible[i]);
+ pw.println("mCarrierRoamingNtnSignalStrength="
+ + mCarrierRoamingNtnSignalStrength[i]);
// We need to obfuscate package names, and primitive arrays' native toString is ugly
Pair<List<String>, int[]> carrierPrivilegeState = mCarrierPrivilegeStates.get(i);
diff --git a/services/core/java/com/android/server/adaptiveauth/OWNERS b/services/core/java/com/android/server/adaptiveauth/OWNERS
deleted file mode 100644
index b188105..0000000
--- a/services/core/java/com/android/server/adaptiveauth/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-hainingc@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 18e8abb..0826c53 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -19307,18 +19307,31 @@
public void addCreatorToken(@Nullable Intent intent, String creatorPackage) {
if (!preventIntentRedirect()) return;
- if (intent == null) return;
- intent.forEachNestedCreatorToken(extraIntent -> {
- IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent, creatorPackage);
- if (creatorToken != null) {
- extraIntent.setCreatorToken(creatorToken);
- // TODO remove Slog.wtf once proven FrameworkStatsLog works. b/375396329
- Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
- + creatorPackage + "; intent: " + intent);
- FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED,
- creatorToken.getCreatorUid());
+ if (intent == null || intent.getExtraIntentKeys() == null) return;
+ for (String key : intent.getExtraIntentKeys()) {
+ try {
+ Intent extraIntent = intent.getParcelableExtra(key, Intent.class);
+ if (extraIntent == null) {
+ Slog.w(TAG, "The key {" + key
+ + "} does not correspond to an intent in the extra bundle.");
+ continue;
+ }
+ IntentCreatorToken creatorToken = createIntentCreatorToken(extraIntent,
+ creatorPackage);
+ if (creatorToken != null) {
+ extraIntent.setCreatorToken(creatorToken);
+ Slog.wtf(TAG, "A creator token is added to an intent. creatorPackage: "
+ + creatorPackage + "; intent: " + intent);
+ FrameworkStatsLog.write(INTENT_CREATOR_TOKEN_ADDED,
+ creatorToken.getCreatorUid());
+ }
+ } catch (Exception e) {
+ Slog.wtf(TAG,
+ "Something went wrong when trying to add creator token for embedded "
+ + "intents of intent: ."
+ + intent, e);
}
- });
+ }
}
private IntentCreatorToken createIntentCreatorToken(Intent intent, String creatorPackage) {
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 8dc7c73..3dd5ec9 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -42,6 +42,7 @@
import static com.android.aconfig_new_storage.Flags.enableAconfigStorageDaemon;
import static com.android.aconfig_new_storage.Flags.supportImmediateLocalOverrides;
import static com.android.aconfig_new_storage.Flags.supportClearLocalOverridesImmediately;
+import static com.android.aconfig.flags.Flags.enableSystemAconfigdRust;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -144,7 +145,6 @@
"android_core_networking",
"android_health_services",
"android_sdk",
- "android_stylus",
"aoc",
"app_widgets",
"arc_next",
@@ -209,6 +209,7 @@
"pixel_continuity",
"pixel_perf",
"pixel_sensors",
+ "pixel_state_server",
"pixel_system_sw_video",
"pixel_video_sw",
"pixel_watch",
@@ -456,9 +457,11 @@
static ProtoInputStream sendAconfigdRequests(ProtoOutputStream requests) {
// connect to aconfigd socket
LocalSocket client = new LocalSocket();
+ String socketName = enableSystemAconfigdRust()
+ ? "aconfigd_system" : "aconfigd";
try{
client.connect(new LocalSocketAddress(
- "aconfigd", LocalSocketAddress.Namespace.RESERVED));
+ socketName, LocalSocketAddress.Namespace.RESERVED));
Slog.d(TAG, "connected to aconfigd socket");
} catch (IOException ioe) {
logErr("failed to connect to aconfigd socket", ioe);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 0cf55bb..6ba3569 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -9193,16 +9193,6 @@
mIndexMin = MIN_STREAM_VOLUME[streamType] * 10;
mIndexMax = MAX_STREAM_VOLUME[streamType] * 10;
- final int status = AudioSystem.initStreamVolume(
- streamType, MIN_STREAM_VOLUME[streamType], MAX_STREAM_VOLUME[streamType]);
- if (status != AudioSystem.AUDIO_STATUS_OK) {
- sLifecycleLogger.enqueue(new EventLogger.StringEvent(
- "VSS() stream:" + streamType + " initStreamVolume=" + status)
- .printLog(ALOGE, TAG));
- sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
- "VSS()" /*obj*/, 2 * INDICATE_SYSTEM_READY_RETRY_DELAY_MS);
- }
-
updateIndexFactors();
mIndexMinNoPerm = mIndexMin; // may be overwritten later in updateNoPermMinIndex()
@@ -9267,6 +9257,19 @@
mIndexMinNoPerm = mIndexMin;
}
}
+
+ final int status = AudioSystem.initStreamVolume(
+ mStreamType, mIndexMin / 10, mIndexMax / 10);
+ sVolumeLogger.enqueue(new EventLogger.StringEvent(
+ "updateIndexFactors() stream:" + mStreamType + " index min/max:"
+ + mIndexMin / 10 + "/" + mIndexMax / 10 + " indexStepFactor:"
+ + mIndexStepFactor).printSlog(ALOGI, TAG));
+ if (status != AudioSystem.AUDIO_STATUS_OK) {
+ sVolumeLogger.enqueue(new EventLogger.StringEvent(
+ "Failed initStreamVolume with status=" + status).printSlog(ALOGE, TAG));
+ sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
+ "updateIndexFactors()" /*obj*/, 2 * INDICATE_SYSTEM_READY_RETRY_DELAY_MS);
+ }
}
/**
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index 00280c8f..6578023 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -288,11 +288,13 @@
* @param handler The handler to run {@link #onChange} on, or null if none.
*/
public SettingObserver(Context context, Handler handler,
- List<BiometricService.EnabledOnKeyguardCallback> callbacks) {
+ List<BiometricService.EnabledOnKeyguardCallback> callbacks,
+ UserManager userManager, FingerprintManager fingerprintManager,
+ FaceManager faceManager) {
super(handler);
mContentResolver = context.getContentResolver();
mCallbacks = callbacks;
- mUserManager = context.getSystemService(UserManager.class);
+ mUserManager = userManager;
final boolean hasFingerprint = context.getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT);
@@ -304,7 +306,7 @@
Build.VERSION.DEVICE_INITIAL_SDK_INT <= Build.VERSION_CODES.Q
&& hasFace && !hasFingerprint;
- addBiometricListenersForMandatoryBiometrics(context);
+ addBiometricListenersForMandatoryBiometrics(context, fingerprintManager, faceManager);
updateContentObserver();
}
@@ -431,11 +433,21 @@
public boolean getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(int userId) {
if (!mMandatoryBiometricsEnabled.containsKey(userId)) {
+ Slog.d(TAG, "update mb toggle for user " + userId);
updateMandatoryBiometricsForAllProfiles(userId);
}
if (!mMandatoryBiometricsRequirementsSatisfied.containsKey(userId)) {
+ Slog.d(TAG, "update mb reqs for user " + userId);
updateMandatoryBiometricsRequirementsForAllProfiles(userId);
}
+
+ Slog.d(TAG, mMandatoryBiometricsEnabled.getOrDefault(userId,
+ DEFAULT_MANDATORY_BIOMETRICS_STATUS)
+ + " " + mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
+ DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS)
+ + " " + getEnabledForApps(userId)
+ + " " + (mFingerprintEnrolledForUser.getOrDefault(userId, false /* default */)
+ || mFaceEnrolledForUser.getOrDefault(userId, false /* default */)));
return mMandatoryBiometricsEnabled.getOrDefault(userId,
DEFAULT_MANDATORY_BIOMETRICS_STATUS)
&& mMandatoryBiometricsRequirementsSatisfied.getOrDefault(userId,
@@ -456,11 +468,23 @@
private void updateMandatoryBiometricsForAllProfiles(int userId) {
int effectiveUserId = userId;
- if (mUserManager.getMainUser() != null) {
- effectiveUserId = mUserManager.getMainUser().getIdentifier();
+ final UserInfo parentProfile = mUserManager.getProfileParent(userId);
+
+ if (parentProfile != null) {
+ effectiveUserId = parentProfile.id;
}
- for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
- mMandatoryBiometricsEnabled.put(profileUserId,
+
+ final int[] enabledProfileIds = mUserManager.getEnabledProfileIds(effectiveUserId);
+ if (enabledProfileIds != null) {
+ for (int profileUserId : enabledProfileIds) {
+ mMandatoryBiometricsEnabled.put(profileUserId,
+ Settings.Secure.getIntForUser(
+ mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
+ DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
+ effectiveUserId) != 0);
+ }
+ } else {
+ mMandatoryBiometricsEnabled.put(userId,
Settings.Secure.getIntForUser(
mContentResolver, Settings.Secure.MANDATORY_BIOMETRICS,
DEFAULT_MANDATORY_BIOMETRICS_STATUS ? 1 : 0,
@@ -470,11 +494,24 @@
private void updateMandatoryBiometricsRequirementsForAllProfiles(int userId) {
int effectiveUserId = userId;
- if (mUserManager.getMainUser() != null) {
- effectiveUserId = mUserManager.getMainUser().getIdentifier();
+ final UserInfo parentProfile = mUserManager.getProfileParent(userId);
+
+ if (parentProfile != null) {
+ effectiveUserId = parentProfile.id;
}
- for (int profileUserId: mUserManager.getEnabledProfileIds(effectiveUserId)) {
- mMandatoryBiometricsRequirementsSatisfied.put(profileUserId,
+
+ final int[] enabledProfileIds = mUserManager.getEnabledProfileIds(effectiveUserId);
+ if (enabledProfileIds != null) {
+ for (int profileUserId : enabledProfileIds) {
+ mMandatoryBiometricsRequirementsSatisfied.put(profileUserId,
+ Settings.Secure.getIntForUser(mContentResolver,
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
+ DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS
+ ? 1 : 0,
+ effectiveUserId) != 0);
+ }
+ } else {
+ mMandatoryBiometricsRequirementsSatisfied.put(userId,
Settings.Secure.getIntForUser(mContentResolver,
Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED,
DEFAULT_MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED_STATUS ? 1 : 0,
@@ -482,10 +519,8 @@
}
}
- private void addBiometricListenersForMandatoryBiometrics(Context context) {
- final FingerprintManager fingerprintManager = context.getSystemService(
- FingerprintManager.class);
- final FaceManager faceManager = context.getSystemService(FaceManager.class);
+ private void addBiometricListenersForMandatoryBiometrics(Context context,
+ FingerprintManager fingerprintManager, FaceManager faceManager) {
if (fingerprintManager != null) {
fingerprintManager.addAuthenticatorsRegisteredCallback(
new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
@@ -1169,7 +1204,9 @@
*/
public SettingObserver getSettingObserver(Context context, Handler handler,
List<EnabledOnKeyguardCallback> callbacks) {
- return new SettingObserver(context, handler, callbacks);
+ return new SettingObserver(context, handler, callbacks, context.getSystemService(
+ UserManager.class), context.getSystemService(FingerprintManager.class),
+ context.getSystemService(FaceManager.class));
}
/**
diff --git a/services/core/java/com/android/server/input/InputGestureManager.java b/services/core/java/com/android/server/input/InputGestureManager.java
index e545dd5..2e7f5c0 100644
--- a/services/core/java/com/android/server/input/InputGestureManager.java
+++ b/services/core/java/com/android/server/input/InputGestureManager.java
@@ -215,6 +215,12 @@
systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_T,
KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_TALKBACK));
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_MINUS,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT));
+ systemShortcuts.add(createKeyGesture(KeyEvent.KEYCODE_EQUALS,
+ KeyEvent.META_META_ON | KeyEvent.META_ALT_ON,
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN));
}
if (keyboardA11yShortcutControl()) {
if (InputSettings.isAccessibilityBounceKeysFeatureEnabled()) {
diff --git a/services/core/java/com/android/server/media/quality/MediaQualityService.java b/services/core/java/com/android/server/media/quality/MediaQualityService.java
index a45ea1d..21ae182 100644
--- a/services/core/java/com/android/server/media/quality/MediaQualityService.java
+++ b/services/core/java/com/android/server/media/quality/MediaQualityService.java
@@ -59,15 +59,15 @@
return pp;
}
@Override
- public void updatePictureProfile(long id, PictureProfile pp) {
+ public void updatePictureProfile(String id, PictureProfile pp) {
// TODO: implement
}
@Override
- public void removePictureProfile(long id) {
+ public void removePictureProfile(String id) {
// TODO: implement
}
@Override
- public PictureProfile getPictureProfileById(long id) {
+ public PictureProfile getPictureProfile(int type, String name) {
return null;
}
@Override
@@ -79,7 +79,7 @@
return new ArrayList<>();
}
@Override
- public List<PictureProfile> getAllPictureProfiles() {
+ public List<String> getPictureProfilePackageNames() {
return new ArrayList<>();
}
@@ -89,15 +89,15 @@
return pp;
}
@Override
- public void updateSoundProfile(long id, SoundProfile pp) {
+ public void updateSoundProfile(String id, SoundProfile pp) {
// TODO: implement
}
@Override
- public void removeSoundProfile(long id) {
+ public void removeSoundProfile(String id) {
// TODO: implement
}
@Override
- public SoundProfile getSoundProfileById(long id) {
+ public SoundProfile getSoundProfileById(String id) {
return null;
}
@Override
@@ -109,7 +109,7 @@
return new ArrayList<>();
}
@Override
- public List<SoundProfile> getAllSoundProfiles() {
+ public List<String> getSoundProfilePackageNames() {
return new ArrayList<>();
}
@@ -138,6 +138,14 @@
return new ArrayList<>();
}
+ @Override
+ public List<String> getPictureProfileAllowList() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public void setPictureProfileAllowList(List<String> packages) {
+ }
@Override
public boolean isSupported() {
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 5febd5c..5914dbe 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -788,6 +788,20 @@
return;
}
+ // Check if summary & child notifications are not part of the same section/bundle
+ // Needs a check here if notification was bundled while enqueued
+ if (notificationRegroupOnClassification()
+ && android.service.notification.Flags.notificationClassification()) {
+ if (isGroupChildBundled(record, summaryByGroupKey)) {
+ if (DEBUG) {
+ Slog.v(TAG, "isGroupChildInDifferentBundleThanSummary: " + record);
+ }
+ moveNotificationsToNewSection(record.getUserId(), pkgName,
+ List.of(new NotificationMoveOp(record, null, fullAggregateGroupKey)));
+ return;
+ }
+ }
+
// scenario 3: sparse/singleton groups
if (Flags.notificationForceGroupSingletons()) {
try {
@@ -800,6 +814,27 @@
}
}
+ private static boolean isGroupChildBundled(final NotificationRecord record,
+ final Map<String, NotificationRecord> summaryByGroupKey) {
+ final StatusBarNotification sbn = record.getSbn();
+ final String groupKey = record.getSbn().getGroupKey();
+
+ if (!sbn.isAppGroup()) {
+ return false;
+ }
+
+ if (record.getNotification().isGroupSummary()) {
+ return false;
+ }
+
+ final NotificationRecord summary = summaryByGroupKey.get(groupKey);
+ if (summary == null) {
+ return false;
+ }
+
+ return NotificationChannel.SYSTEM_RESERVED_IDS.contains(record.getChannel().getId());
+ }
+
/**
* Called when a notification is removed, so that this helper can adjust the aggregate groups:
* - Removes the autogroup summary of the notification's section
@@ -1598,7 +1633,7 @@
final int mSummaryId;
private final Predicate<NotificationRecord> mSectionChecker;
- public NotificationSectioner(String name, int summaryId,
+ private NotificationSectioner(String name, int summaryId,
Predicate<NotificationRecord> sectionChecker) {
mName = name;
mSummaryId = summaryId;
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 93482e7..b0ef807 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -75,9 +75,7 @@
import com.android.internal.util.function.TriPredicate;
import com.android.modules.utils.TypedXmlPullParser;
import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.LocalServices;
import com.android.server.notification.NotificationManagerService.DumpFilter;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.utils.TimingsTraceAndSlog;
import org.xmlpull.v1.XmlPullParser;
@@ -136,7 +134,6 @@
private final UserProfiles mUserProfiles;
protected final IPackageManager mPm;
protected final UserManager mUm;
- private final UserManagerInternal mUserManagerInternal;
private final Config mConfig;
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -198,7 +195,6 @@
mConfig = getConfig();
mApprovalLevel = APPROVAL_BY_COMPONENT;
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
}
abstract protected Config getConfig();
@@ -1389,14 +1385,9 @@
@GuardedBy("mMutex")
protected void populateComponentsToBind(SparseArray<Set<ComponentName>> componentsToBind,
final IntArray activeUsers,
- SparseArray<ArraySet<ComponentName>> approvedComponentsByUser,
- boolean isVisibleBackgroundUser) {
- // When it is a visible background user in Automotive MUMD environment,
- // don't clear mEnabledServicesForCurrentProfile and mEnabledServicesPackageNames.
- if (!isVisibleBackgroundUser) {
- mEnabledServicesForCurrentProfiles.clear();
- mEnabledServicesPackageNames.clear();
- }
+ SparseArray<ArraySet<ComponentName>> approvedComponentsByUser) {
+ mEnabledServicesForCurrentProfiles.clear();
+ mEnabledServicesPackageNames.clear();
final int nUserIds = activeUsers.size();
for (int i = 0; i < nUserIds; ++i) {
@@ -1417,12 +1408,7 @@
}
componentsToBind.put(userId, add);
- // When it is a visible background user in Automotive MUMD environment,
- // skip adding items to mEnabledServicesForCurrentProfile
- // and mEnabledServicesPackageNames.
- if (isVisibleBackgroundUser) {
- continue;
- }
+
mEnabledServicesForCurrentProfiles.addAll(userComponents);
for (int j = 0; j < userComponents.size(); j++) {
@@ -1470,10 +1456,7 @@
IntArray userIds = mUserProfiles.getCurrentProfileIds();
boolean rebindAllCurrentUsers = mUserProfiles.isProfileUser(userToRebind, mContext)
&& allowRebindForParentUser();
- boolean isVisibleBackgroundUser = false;
if (userToRebind != USER_ALL && !rebindAllCurrentUsers) {
- isVisibleBackgroundUser =
- mUserManagerInternal.isVisibleBackgroundFullUser(userToRebind);
userIds = new IntArray(1);
userIds.add(userToRebind);
}
@@ -1488,8 +1471,7 @@
// Filter approvedComponentsByUser to collect all of the components that are allowed
// for the currently active user(s).
- populateComponentsToBind(componentsToBind, userIds, approvedComponentsByUser,
- isVisibleBackgroundUser);
+ populateComponentsToBind(componentsToBind, userIds, approvedComponentsByUser);
// For every current non-system connection, disconnect services that are no longer
// approved, or ALL services if we are force rebinding
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 845798f..8d039f1 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5859,8 +5859,8 @@
public int interceptMotionBeforeQueueingNonInteractive(int displayId, int source, int action,
long whenNanos, int policyFlags) {
if ((policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(
- whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(displayId, whenNanos / 1000000, source,
+ action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -5874,8 +5874,8 @@
// there will be no dream to intercept the touch and wake into ambient. The device should
// wake up in this case.
if (isTheaterModeEnabled() && (policyFlags & FLAG_WAKE) != 0) {
- if (mWindowWakeUpPolicy.wakeUpFromMotion(
- whenNanos / 1000000, source, action == MotionEvent.ACTION_DOWN)) {
+ if (mWindowWakeUpPolicy.wakeUpFromMotion(displayId, whenNanos / 1000000, source,
+ action == MotionEvent.ACTION_DOWN)) {
// Woke up. Pass motion events to user.
return ACTION_PASS_TO_USER;
}
@@ -6223,7 +6223,7 @@
}
private void wakeUpFromWakeKey(long eventTime, int keyCode, boolean isDown) {
- if (mWindowWakeUpPolicy.wakeUpFromKey(eventTime, keyCode, isDown)) {
+ if (mWindowWakeUpPolicy.wakeUpFromKey(DEFAULT_DISPLAY, eventTime, keyCode, isDown)) {
final boolean keyCanLaunchHome = keyCode == KEYCODE_HOME || keyCode == KEYCODE_POWER;
// Start HOME with "reason" extra if sleeping for more than mWakeUpToLastStateTimeout
if (shouldWakeUpWithHomeIntent() && keyCanLaunchHome) {
diff --git a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
index af1ad13..04dbd1f 100644
--- a/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowWakeUpPolicy.java
@@ -25,6 +25,7 @@
import static android.view.KeyEvent.KEYCODE_POWER;
import static com.android.server.policy.Flags.supportInputWakeupDelegate;
+import static com.android.server.power.feature.flags.Flags.perDisplayWakeByTouch;
import android.annotation.Nullable;
import android.content.Context;
@@ -107,13 +108,14 @@
/**
* Wakes up from a key event.
*
+ * @param displayId the id of the display to wake.
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
* @param keyCode the {@link android.view.KeyEvent} key code of the key event.
* @param isDown {@code true} if the event's action is {@link KeyEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromKey(long eventTime, int keyCode, boolean isDown) {
+ boolean wakeUpFromKey(int displayId, long eventTime, int keyCode, boolean isDown) {
final boolean wakeAllowedDuringTheaterMode =
keyCode == KEYCODE_POWER
? mAllowTheaterModeWakeFromPowerKey
@@ -126,22 +128,31 @@
&& mInputWakeUpDelegate.wakeUpFromKey(eventTime, keyCode, isDown)) {
return true;
}
- wakeUp(
- eventTime,
- keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
- keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+ if (perDisplayWakeByTouch()) {
+ wakeUp(
+ displayId,
+ eventTime,
+ keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
+ keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+ } else {
+ wakeUp(
+ eventTime,
+ keyCode == KEYCODE_POWER ? WAKE_REASON_POWER_BUTTON : WAKE_REASON_WAKE_KEY,
+ keyCode == KEYCODE_POWER ? "POWER" : "KEY");
+ }
return true;
}
/**
* Wakes up from a motion event.
*
+ * @param displayId the id of the display to wake.
* @param eventTime the timestamp of the event in {@link SystemClock#uptimeMillis()}.
* @param isDown {@code true} if the event's action is {@link MotionEvent#ACTION_DOWN}.
* @return {@code true} if the policy allows the requested wake up and the request has been
* executed; {@code false} otherwise.
*/
- boolean wakeUpFromMotion(long eventTime, int source, boolean isDown) {
+ boolean wakeUpFromMotion(int displayId, long eventTime, int source, boolean isDown) {
if (!canWakeUp(mAllowTheaterModeWakeFromMotion)) {
if (DEBUG) Slog.d(TAG, "Unable to wake up from motion.");
return false;
@@ -150,7 +161,11 @@
&& mInputWakeUpDelegate.wakeUpFromMotion(eventTime, source, isDown)) {
return true;
}
- wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ if (perDisplayWakeByTouch()) {
+ wakeUp(displayId, eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ } else {
+ wakeUp(eventTime, WAKE_REASON_WAKE_MOTION, "MOTION");
+ }
return true;
}
@@ -237,4 +252,12 @@
private void wakeUp(long wakeTime, @WakeReason int reason, String details) {
mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details);
}
+
+ /** Wakes up given display. */
+ private void wakeUp(int displayId, long wakeTime, @WakeReason int reason, String details) {
+ // If we're given an invalid display id to wake, fall back to waking default display
+ final int displayIdToWake =
+ displayId == Display.INVALID_DISPLAY ? Display.DEFAULT_DISPLAY : displayId;
+ mPowerManager.wakeUp(wakeTime, reason, "android.policy:" + details, displayIdToWake);
+ }
}
diff --git a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java b/services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java
similarity index 96%
rename from services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
rename to services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java
index 9398c7a..b129fdc 100644
--- a/services/core/java/com/android/server/adaptiveauth/AdaptiveAuthService.java
+++ b/services/core/java/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationService.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.adaptiveauth;
+package com.android.server.security.adaptiveauthentication;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
@@ -55,8 +55,8 @@
/**
* @hide
*/
-public class AdaptiveAuthService extends SystemService {
- private static final String TAG = "AdaptiveAuthService";
+public class AdaptiveAuthenticationService extends SystemService {
+ private static final String TAG = "AdaptiveAuthenticationService";
private static final boolean DEBUG = Build.IS_DEBUGGABLE && Log.isLoggable(TAG, Log.DEBUG);
@VisibleForTesting
@@ -78,12 +78,12 @@
final SparseIntArray mFailedAttemptsForUser = new SparseIntArray();
private final SparseLongArray mLastLockedTimestamp = new SparseLongArray();
- public AdaptiveAuthService(Context context) {
+ public AdaptiveAuthenticationService(Context context) {
this(context, new LockPatternUtils(context));
}
@VisibleForTesting
- public AdaptiveAuthService(Context context, LockPatternUtils lockPatternUtils) {
+ public AdaptiveAuthenticationService(Context context, LockPatternUtils lockPatternUtils) {
super(context);
mLockPatternUtils = lockPatternUtils;
mLockSettings = Objects.requireNonNull(
diff --git a/services/core/java/com/android/server/security/adaptiveauthentication/OWNERS b/services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
new file mode 100644
index 0000000..29affcd
--- /dev/null
+++ b/services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
@@ -0,0 +1,3 @@
+hainingc@google.com
+jbolinger@google.com
+graciecheng@google.com
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index ec171c5..a71620d 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -46,10 +46,8 @@
import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
import static com.android.window.flags.Flags.balAdditionalStartModes;
import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg;
-import static com.android.window.flags.Flags.balImproveRealCallerVisibilityCheck;
import static com.android.window.flags.Flags.balImprovedMetrics;
import static com.android.window.flags.Flags.balRequireOptInByPendingIntentCreator;
-import static com.android.window.flags.Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid;
import static com.android.window.flags.Flags.balShowToastsBlocked;
import static com.android.window.flags.Flags.balStrictModeRo;
@@ -348,11 +346,7 @@
@BackgroundActivityStartMode int realCallerBackgroundActivityStartMode =
checkedOptions.getPendingIntentBackgroundActivityStartMode();
- if (!balImproveRealCallerVisibilityCheck()) {
- // without this fix the auto-opt ins below would violate CTS tests
- mAutoOptInReason = null;
- mAutoOptInCaller = false;
- } else if (originatingPendingIntent == null) {
+ if (originatingPendingIntent == null) {
mAutoOptInReason = AUTO_OPT_IN_NOT_PENDING_INTENT;
mAutoOptInCaller = true;
} else if (mIsCallForResult) {
@@ -599,12 +593,8 @@
mCheckedOptions.getPendingIntentBackgroundActivityStartMode()));
}
// features
- sb.append("; balImproveRealCallerVisibilityCheck: ")
- .append(balImproveRealCallerVisibilityCheck());
sb.append("; balRequireOptInByPendingIntentCreator: ")
.append(balRequireOptInByPendingIntentCreator());
- sb.append("; balRespectAppSwitchStateWhenCheckBoundByForegroundUid: ")
- .append(balRespectAppSwitchStateWhenCheckBoundByForegroundUid());
sb.append("; balDontBringExistingBackgroundTaskStackToFg: ")
.append(balDontBringExistingBackgroundTaskStackToFg());
sb.append("]");
@@ -1133,23 +1123,13 @@
final boolean appSwitchAllowedOrFg = state.mAppSwitchState == APP_SWITCH_ALLOW
|| state.mAppSwitchState == APP_SWITCH_FG_ONLY
|| isHomeApp(state.mRealCallingUid, state.mRealCallingPackage);
- if (balImproveRealCallerVisibilityCheck()) {
- if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
- /*background*/ false, "realCallingUid has visible window");
- }
- if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
- return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
- /*background*/ false, "realCallingUid has non-app visible window");
- }
- } else {
- // don't abort if the realCallingUid has a visible window
- // TODO(b/171459802): We should check appSwitchAllowed also
- if (state.mRealCallingUidHasAnyVisibleWindow) {
- return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
- /*background*/ false,
- "realCallingUid has visible (non-toast) window.");
- }
+ if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) {
+ return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW,
+ /*background*/ false, "realCallingUid has visible window");
+ }
+ if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) {
+ return new BalVerdict(BAL_ALLOW_NON_APP_VISIBLE_WINDOW,
+ /*background*/ false, "realCallingUid has non-app visible window");
}
// Don't abort if the realCallerApp or other processes of that uid are considered to be in
diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
index 264c8be..ccf1aed 100644
--- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
+++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java
@@ -50,7 +50,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.server.wm.BackgroundActivityStartController.BalVerdict;
-import com.android.window.flags.Flags;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -137,10 +136,8 @@
}
// Allow if the caller is bound by a UID that's currently foreground.
// But still respect the appSwitchState.
- if (checkConfiguration.checkVisibility && (
- Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid()
- ? appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid()
- : isBoundByForegroundUid())) {
+ if (checkConfiguration.checkVisibility && appSwitchState != APP_SWITCH_DISALLOW
+ && isBoundByForegroundUid()) {
return new BalVerdict(balImprovedMetrics() ? BAL_ALLOW_BOUND_BY_FOREGROUND
: BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false,
"process bound by foreground uid");
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 221b848..9759772 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -119,7 +119,6 @@
import com.android.internal.widget.LockSettingsInternal;
import com.android.server.accessibility.AccessibilityManagerService;
import com.android.server.accounts.AccountManagerService;
-import com.android.server.adaptiveauth.AdaptiveAuthService;
import com.android.server.adb.AdbService;
import com.android.server.alarm.AlarmManagerService;
import com.android.server.am.ActivityManagerService;
@@ -250,6 +249,7 @@
import com.android.server.security.FileIntegrityService;
import com.android.server.security.KeyAttestationApplicationIdProviderService;
import com.android.server.security.KeyChainSystemService;
+import com.android.server.security.adaptiveauthentication.AdaptiveAuthenticationService;
import com.android.server.security.advancedprotection.AdvancedProtectionService;
import com.android.server.security.rkp.RemoteProvisioningService;
import com.android.server.selinux.SelinuxAuditLogsService;
@@ -2651,8 +2651,8 @@
t.traceEnd();
if (android.adaptiveauth.Flags.enableAdaptiveAuth()) {
- t.traceBegin("StartAdaptiveAuthService");
- mSystemServiceManager.startService(AdaptiveAuthService.class);
+ t.traceBegin("StartAdaptiveAuthenticationService");
+ mSystemServiceManager.startService(AdaptiveAuthenticationService.class);
t.traceEnd();
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index dcbc234..2a825f3 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -1308,8 +1308,6 @@
Intent intent = new Intent();
Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
intent.putExtra("EXTRA_INTENT0", extraIntent);
- Intent nestedIntent = new Intent("NESTED_INTENT_ACTION");
- extraIntent.putExtra("NESTED_INTENT", nestedIntent);
intent.collectExtraIntentKeys();
mAms.addCreatorToken(intent, TEST_PACKAGE);
@@ -1319,11 +1317,6 @@
assertThat(token).isNotNull();
assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
-
- token = (ActivityManagerService.IntentCreatorToken) nestedIntent.getCreatorToken();
- assertThat(token).isNotNull();
- assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid());
- assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE);
}
@Test
@@ -1356,8 +1349,6 @@
Intent intent = new Intent();
Intent extraIntent = new Intent("EXTRA_INTENT_ACTION");
intent.putExtra("EXTRA_INTENT", extraIntent);
- Intent nestedIntent = new Intent("NESTED_INTENT_ACTION");
- extraIntent.putExtra("NESTED_INTENT", nestedIntent);
intent.collectExtraIntentKeys();
@@ -1383,12 +1374,9 @@
extraIntent = intent.getParcelableExtra("EXTRA_INTENT", Intent.class);
extraIntent2 = intent.getParcelableExtra("EXTRA_INTENT2", Intent.class);
extraIntent3 = intent.getParcelableExtra("EXTRA_INTENT3", Intent.class);
- nestedIntent = extraIntent.getParcelableExtra("NESTED_INTENT", Intent.class);
assertThat(extraIntent.getExtendedFlags()
& Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
- assertThat(nestedIntent.getExtendedFlags()
- & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0);
// sneaked in intent should have EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN set.
assertThat(extraIntent2.getExtendedFlags()
& Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isNotEqualTo(0);
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index c645c08..9b7bbe0 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -114,6 +114,7 @@
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.CREATE_VIRTUAL_DEVICE" />
+ <uses-permission android:name="android.permission.MANAGE_KEY_GESTURES" />
<queries>
<package android:name="com.android.servicestests.apps.suspendtestapp" />
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
index 8164ef9..f0d3456 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationControllerTest.java
@@ -19,9 +19,13 @@
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
import static com.android.server.wm.WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -660,6 +664,90 @@
}
@Test
+ public void scaleMagnificationByStep_fullscreenMode_stepInAndOut() throws RemoteException {
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, 1.0f, false);
+ reset(mScreenMagnificationController);
+
+ // Verify the zoom scale factor increases by
+ // {@code MagnificationController.DefaultMagnificationScaleStepProvider
+ // .ZOOM_STEP_SCALE_FACTOR} and the center coordinates are
+ // unchanged (Float.NaN as values denotes unchanged center).
+ mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN);
+ verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
+ eq(MagnificationController
+ .DefaultMagnificationScaleStepProvider.ZOOM_STEP_SCALE_FACTOR),
+ eq(Float.NaN), eq(Float.NaN), anyBoolean(), anyInt());
+
+ mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT);
+ verify(mScreenMagnificationController).setScaleAndCenter(eq(TEST_DISPLAY),
+ eq(SCALE_MIN_VALUE), eq(Float.NaN), eq(Float.NaN), anyBoolean(), anyInt());
+ }
+
+ @Test
+ public void scaleMagnificationByStep_testMaxScaling() throws RemoteException {
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MIN_VALUE, false);
+ reset(mScreenMagnificationController);
+
+ float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ while (currentScale < SCALE_MAX_VALUE) {
+ assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN)).isTrue();
+ final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ assertThat(nextScale).isGreaterThan(currentScale);
+ currentScale = nextScale;
+ }
+
+ assertThat(currentScale).isEqualTo(SCALE_MAX_VALUE);
+ assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN)).isFalse();
+ }
+
+ @Test
+ public void scaleMagnificationByStep_testMinScaling() throws RemoteException {
+ setMagnificationEnabled(MODE_FULLSCREEN);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MAX_VALUE, false);
+ reset(mScreenMagnificationController);
+
+ float currentScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ while (currentScale > SCALE_MIN_VALUE) {
+ assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT)).isTrue();
+ final float nextScale = mScreenMagnificationController.getScale(TEST_DISPLAY);
+ assertThat(nextScale).isLessThan(currentScale);
+ currentScale = nextScale;
+ }
+
+ assertThat(currentScale).isEqualTo(SCALE_MIN_VALUE);
+ assertThat(mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT)).isFalse();
+ }
+
+ @Test
+ public void scaleMagnificationByStep_windowedMode_stepInAndOut() throws RemoteException {
+ setMagnificationEnabled(MODE_WINDOW);
+ mMagnificationController.onPerformScaleAction(TEST_DISPLAY, SCALE_MIN_VALUE, false);
+ reset(mMagnificationConnectionManager);
+
+ // Verify the zoom scale factor increases by
+ // {@code MagnificationController.DefaultMagnificationScaleStepProvider
+ // .ZOOM_STEP_SCALE_FACTOR}.
+ mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_IN);
+ verify(mMagnificationConnectionManager).setScale(eq(TEST_DISPLAY),
+ eq(MagnificationController
+ .DefaultMagnificationScaleStepProvider.ZOOM_STEP_SCALE_FACTOR));
+
+ mMagnificationController.scaleMagnificationByStep(TEST_DISPLAY,
+ MagnificationController.ZOOM_DIRECTION_OUT);
+ verify(mMagnificationConnectionManager).setScale(eq(TEST_DISPLAY),
+ eq(SCALE_MIN_VALUE));
+ }
+
+ @Test
public void enableWindowMode_notifyMagnificationChanged() throws RemoteException {
setMagnificationEnabled(MODE_WINDOW);
diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS b/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS
deleted file mode 100644
index 0218a78..0000000
--- a/services/tests/servicestests/src/com/android/server/adaptiveauth/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-include /services/core/java/com/android/server/adaptiveauth/OWNERS
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index bc410d9..88829c1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -62,6 +62,7 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.Flags;
import android.hardware.biometrics.IBiometricAuthenticator;
import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -70,8 +71,16 @@
import android.hardware.biometrics.IBiometricServiceReceiver;
import android.hardware.biometrics.IBiometricSysuiReceiver;
import android.hardware.biometrics.PromptInfo;
+import android.hardware.biometrics.SensorProperties;
import android.hardware.display.DisplayManagerGlobal;
+import android.hardware.face.FaceManager;
+import android.hardware.face.FaceSensorProperties;
+import android.hardware.face.FaceSensorPropertiesInternal;
+import android.hardware.face.IFaceAuthenticatorsRegisteredCallback;
import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
import android.hardware.keymaster.HardwareAuthenticatorType;
import android.os.Binder;
import android.os.Handler;
@@ -84,6 +93,7 @@
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.platform.test.flag.junit.SetFlagsRule;
+import android.provider.Settings;
import android.security.GateKeeper;
import android.security.KeyStoreAuthorization;
import android.service.gatekeeper.IGateKeeperService;
@@ -93,6 +103,7 @@
import android.view.DisplayInfo;
import android.view.WindowManager;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
@@ -111,6 +122,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@@ -144,6 +156,27 @@
private static final int SENSOR_ID_FINGERPRINT = 0;
private static final int SENSOR_ID_FACE = 1;
+ private final ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback.Stub>
+ mFingerprintAuthenticatorRegisteredCallbackCaptor = ArgumentCaptor.forClass(
+ IFingerprintAuthenticatorsRegisteredCallback.Stub.class);
+ private final ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback.Stub>
+ mFaceAuthenticatorRegisteredCallbackCaptor = ArgumentCaptor.forClass(
+ IFaceAuthenticatorsRegisteredCallback.Stub.class);
+ private final ArgumentCaptor<BiometricStateListener> mBiometricStateListenerArgumentCaptor =
+ ArgumentCaptor.forClass(BiometricStateListener.class);
+ private final List<FingerprintSensorPropertiesInternal>
+ mFingerprintSensorPropertiesInternals = List.of(
+ new FingerprintSensorPropertiesInternal(SENSOR_ID_FINGERPRINT,
+ SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FingerprintSensorProperties.TYPE_UNKNOWN,
+ true /* resetLockoutRequiresHardwareAuthToken */));
+ private final List<FaceSensorPropertiesInternal>
+ mFaceSensorPropertiesInternals = List.of(
+ new FaceSensorPropertiesInternal(SENSOR_ID_FACE,
+ SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */,
+ List.of(), FaceSensorProperties.TYPE_UNKNOWN,
+ false /* supportsFaceDetection */, false /* supportsSelfIllumination */,
+ false /* resetLockoutRequiresChallenge */));
private BiometricService mBiometricService;
@@ -192,6 +225,10 @@
@Mock
private BiometricNotificationLogger mNotificationLogger;
+ @Mock
+ private FingerprintManager mFingerprintManager;
+ @Mock
+ private FaceManager mFaceManager;
BiometricContextProvider mBiometricContextProvider;
@@ -1975,6 +2012,59 @@
eq(hardwareAuthenticators));
}
+ @Test
+ public void testMandatoryBiometricsValue_whenParentProfileEnabled() throws RemoteException {
+ final Context context = ApplicationProvider.getApplicationContext();
+ final int profileParentId = context.getContentResolver().getUserId();
+ final int userId = profileParentId + 1;
+ final BiometricService.SettingObserver settingObserver =
+ new BiometricService.SettingObserver(
+ context, mBiometricHandlerProvider.getBiometricCallbackHandler(),
+ new ArrayList<>(), mUserManager, mFingerprintManager, mFaceManager);
+
+ verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(
+ mFingerprintAuthenticatorRegisteredCallbackCaptor.capture());
+ verify(mFaceManager).addAuthenticatorsRegisteredCallback(
+ mFaceAuthenticatorRegisteredCallbackCaptor.capture());
+
+ mFingerprintAuthenticatorRegisteredCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
+ mFingerprintSensorPropertiesInternals);
+ mFaceAuthenticatorRegisteredCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
+ mFaceSensorPropertiesInternals);
+
+ verify(mFingerprintManager).registerBiometricStateListener(
+ mBiometricStateListenerArgumentCaptor.capture());
+
+ mBiometricStateListenerArgumentCaptor.getValue().onEnrollmentsChanged(userId,
+ SENSOR_ID_FINGERPRINT, true /* hasEnrollments */);
+
+ verify(mFaceManager).registerBiometricStateListener(
+ mBiometricStateListenerArgumentCaptor.capture());
+
+ mBiometricStateListenerArgumentCaptor.getValue().onEnrollmentsChanged(userId,
+ SENSOR_ID_FACE, true /* hasEnrollments */);
+
+ when(mUserManager.getProfileParent(userId)).thenReturn(new UserInfo(profileParentId,
+ "", 0));
+ when(mUserManager.getEnabledProfileIds(profileParentId)).thenReturn(new int[]{userId});
+
+ //Disable Identity Check for profile user
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.MANDATORY_BIOMETRICS, 0, userId);
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, 0,
+ userId);
+ //Enable Identity Check for parent user
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.MANDATORY_BIOMETRICS, 1, profileParentId);
+ Settings.Secure.putIntForUser(context.getContentResolver(),
+ Settings.Secure.MANDATORY_BIOMETRICS_REQUIREMENTS_SATISFIED, 1,
+ profileParentId);
+
+ assertTrue(settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
+ userId));
+ }
+
// Helper methods
private int invokeCanAuthenticate(BiometricService service, int authenticators)
diff --git a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java
similarity index 94%
rename from services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java
rename to services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java
index d180688..154494a 100644
--- a/services/tests/servicestests/src/com/android/server/adaptiveauth/AdaptiveAuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/AdaptiveAuthenticationServiceTest.java
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-package com.android.server.adaptiveauth;
+package com.android.server.security.adaptiveauthentication;
import static android.adaptiveauth.Flags.FLAG_ENABLE_ADAPTIVE_AUTH;
import static android.adaptiveauth.Flags.FLAG_REPORT_BIOMETRIC_AUTH_ATTEMPTS;
import static android.security.Flags.FLAG_REPORT_PRIMARY_AUTH_ATTEMPTS;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST;
-import static com.android.server.adaptiveauth.AdaptiveAuthService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;
+import static com.android.server.security.adaptiveauthentication.AdaptiveAuthenticationService.MAX_ALLOWED_FAILED_AUTH_ATTEMPTS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
@@ -66,12 +66,12 @@
import org.mockito.MockitoAnnotations;
/**
- * atest FrameworksServicesTests:AdaptiveAuthServiceTest
+ * atest FrameworksServicesTests:AdaptiveAuthenticationServiceTest
*/
@Presubmit
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class AdaptiveAuthServiceTest {
+public class AdaptiveAuthenticationServiceTest {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -81,7 +81,7 @@
private static final int REASON_UNKNOWN = 0; // BiometricRequestConstants.RequestReason
private Context mContext;
- private AdaptiveAuthService mAdaptiveAuthService;
+ private AdaptiveAuthenticationService mAdaptiveAuthenticationService;
@Mock
LockPatternUtils mLockPatternUtils;
@@ -124,8 +124,9 @@
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.addService(UserManagerInternal.class, mUserManager);
- mAdaptiveAuthService = new AdaptiveAuthService(mContext, mLockPatternUtils);
- mAdaptiveAuthService.init();
+ mAdaptiveAuthenticationService = new AdaptiveAuthenticationService(
+ mContext, mLockPatternUtils);
+ mAdaptiveAuthenticationService.init();
verify(mLockSettings).registerLockSettingsStateListener(
mLockSettingsStateListenerCaptor.capture());
@@ -317,13 +318,13 @@
private void verifyNotLockDevice(int expectedCntFailedAttempts, int userId) {
assertEquals(expectedCntFailedAttempts,
- mAdaptiveAuthService.mFailedAttemptsForUser.get(userId));
+ mAdaptiveAuthenticationService.mFailedAttemptsForUser.get(userId));
verify(mWindowManager, never()).lockNow();
}
private void verifyLockDevice(int userId) {
assertEquals(MAX_ALLOWED_FAILED_AUTH_ATTEMPTS,
- mAdaptiveAuthService.mFailedAttemptsForUser.get(userId));
+ mAdaptiveAuthenticationService.mFailedAttemptsForUser.get(userId));
verify(mLockPatternUtils).requireStrongAuth(
eq(SOME_AUTH_REQUIRED_AFTER_ADAPTIVE_AUTH_REQUEST), eq(userId));
// If userId is MANAGED_PROFILE_USER_ID, the StrongAuthFlag of its parent (PRIMARY_USER_ID)
diff --git a/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS
new file mode 100644
index 0000000..bc8efa9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/security/adaptiveauthentication/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/security/adaptiveauthentication/OWNERS
\ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
index c9d5241..b3ec215 100644
--- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
+++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java
@@ -30,7 +30,6 @@
import androidx.test.InstrumentationRegistry;
-import com.android.server.pm.UserManagerInternal;
import com.android.server.uri.UriGrantsManagerInternal;
import org.junit.After;
@@ -42,7 +41,6 @@
public class UiServiceTestCase {
@Mock protected PackageManagerInternal mPmi;
- @Mock protected UserManagerInternal mUmi;
@Mock protected UriGrantsManagerInternal mUgmInternal;
protected static final String PKG_N_MR1 = "com.example.n_mr1";
@@ -94,8 +92,6 @@
}
});
- LocalServices.removeServiceForTest(UserManagerInternal.class);
- LocalServices.addService(UserManagerInternal.class, mUmi);
LocalServices.removeServiceForTest(UriGrantsManagerInternal.class);
LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal);
when(mUgmInternal.checkGrantUriPermission(
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 38ff3a2..cc02865 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -2410,11 +2410,13 @@
NotificationChannel.NEWS_ID);
for (NotificationRecord record: notificationList) {
if (record.getChannel().getId().equals(channel1.getId())
+ && record.getNotification().isGroupChild()
&& record.getSbn().getId() % 2 == 0) {
record.updateNotificationChannel(socialChannel);
mGroupHelper.onChannelUpdated(record);
}
if (record.getChannel().getId().equals(channel1.getId())
+ && record.getNotification().isGroupChild()
&& record.getSbn().getId() % 2 != 0) {
record.updateNotificationChannel(newsChannel);
mGroupHelper.onChannelUpdated(record);
@@ -2474,7 +2476,8 @@
NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
IMPORTANCE_DEFAULT);
for (NotificationRecord record: notificationList) {
- if (record.getChannel().getId().equals(channel1.getId())) {
+ if (record.getChannel().getId().equals(channel1.getId())
+ && record.getNotification().isGroupChild()) {
record.updateNotificationChannel(socialChannel);
mGroupHelper.onChannelUpdated(record);
}
@@ -2532,7 +2535,8 @@
BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
NotificationChannel.SOCIAL_MEDIA_ID);
for (NotificationRecord record: notificationList) {
- if (record.getOriginalGroupKey().contains("testGrp")) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
record.updateNotificationChannel(socialChannel);
mGroupHelper.onChannelUpdated(record);
}
@@ -2631,7 +2635,8 @@
BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
NotificationChannel.SOCIAL_MEDIA_ID);
for (NotificationRecord record: notificationList) {
- if (record.getOriginalGroupKey().contains("testGrp")) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
record.updateNotificationChannel(socialChannel);
mGroupHelper.onChannelUpdated(record);
}
@@ -2650,6 +2655,64 @@
}
@Test
+ @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING,
+ FLAG_NOTIFICATION_REGROUP_ON_CLASSIFICATION,
+ FLAG_NOTIFICATION_CLASSIFICATION})
+ public void testValidGroupsRegrouped_notificationBundledWhileEnqueued() {
+ // Check that valid group notifications are regrouped if classification is done
+ // before onNotificationPostedWithDelay (within DELAY_FOR_ASSISTANT_TIME)
+ final List<NotificationRecord> notificationList = new ArrayList<>();
+ final ArrayMap<String, NotificationRecord> summaryByGroup = new ArrayMap<>();
+ final String pkg = "package";
+
+ final int summaryId = 0;
+ final int numChildren = 3;
+ // Post a regular/valid group: summary + notifications
+ NotificationRecord summary = getNotificationRecord(pkg, summaryId,
+ String.valueOf(summaryId), UserHandle.SYSTEM, "testGrp", true);
+ notificationList.add(summary);
+ summaryByGroup.put(summary.getGroupKey(), summary);
+ for (int i = 0; i < numChildren; i++) {
+ NotificationRecord child = getNotificationRecord(pkg, i + 42, String.valueOf(i + 42),
+ UserHandle.SYSTEM, "testGrp", false);
+ notificationList.add(child);
+ }
+
+ // Classify/bundle child notifications. Don't call onChannelUpdated,
+ // adjustments applied while enqueued will use NotificationAdjustmentExtractor.
+ final NotificationChannel socialChannel = new NotificationChannel(
+ NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+ IMPORTANCE_DEFAULT);
+ final String expectedGroupKey_social = GroupHelper.getFullAggregateGroupKey(pkg,
+ AGGREGATE_GROUP_KEY + "SocialSection", UserHandle.SYSTEM.getIdentifier());
+ final NotificationAttributes expectedSummaryAttr_social = new NotificationAttributes(
+ BASE_FLAGS, mSmallIcon, COLOR_DEFAULT, DEFAULT_VISIBILITY, DEFAULT_GROUP_ALERT,
+ NotificationChannel.SOCIAL_MEDIA_ID);
+ for (NotificationRecord record: notificationList) {
+ if (record.getOriginalGroupKey().contains("testGrp")
+ && record.getNotification().isGroupChild()) {
+ record.updateNotificationChannel(socialChannel);
+ }
+ }
+
+ // Check that notifications are forced grouped and app-provided summaries are canceled
+ for (NotificationRecord record: notificationList) {
+ mGroupHelper.onNotificationPostedWithDelay(record, notificationList, summaryByGroup);
+ }
+
+ verify(mCallback, times(1)).addAutoGroupSummary(anyInt(), eq(pkg), anyString(),
+ eq(expectedGroupKey_social), anyInt(), eq(expectedSummaryAttr_social));
+ verify(mCallback, times(numChildren)).addAutoGroup(anyString(), eq(expectedGroupKey_social),
+ eq(true));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString(), anyString());
+ verify(mCallback, times(numChildren - 1)).updateAutogroupSummary(anyInt(), anyString(),
+ anyString(), any());
+ verify(mCallback, times(numChildren)).removeAppProvidedSummaryOnClassification(anyString(),
+ anyString());
+ }
+
+ @Test
@EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
public void testMoveAggregateGroups_updateChannel_groupsUngrouped() {
final String pkg = "package";
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 48bc9d7..e5c42082 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -1484,7 +1484,6 @@
assertTrue(componentsToUnbind.get(0).contains(ComponentName.unflattenFromString("c/c")));
}
- @SuppressWarnings("GuardedBy")
@Test
public void populateComponentsToBind() {
ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
@@ -1508,8 +1507,7 @@
SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
- service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser,
- /* isVisibleBackgroundUser= */ false);
+ service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser);
assertEquals(2, componentsToBind.size());
assertEquals(1, componentsToBind.get(0).size());
@@ -1519,33 +1517,6 @@
assertTrue(componentsToBind.get(10).contains(ComponentName.unflattenFromString("c/c")));
}
- @SuppressWarnings("GuardedBy")
- @Test
- public void populateComponentsToBind_isVisibleBackgroundUser_addComponentsToBindButNotAddToEnabledComponent() {
- ManagedServices service = new TestManagedServices(getContext(), mLock, mUserProfiles, mIpm,
- APPROVAL_BY_COMPONENT);
-
- SparseArray<ArraySet<ComponentName>> approvedComponentsByUser = new SparseArray<>();
- ArraySet<ComponentName> allowed = new ArraySet<>();
- allowed.add(ComponentName.unflattenFromString("pkg1/cmp1"));
- approvedComponentsByUser.put(11, allowed);
- IntArray users = new IntArray();
- users.add(11);
-
- SparseArray<Set<ComponentName>> componentsToBind = new SparseArray<>();
-
- service.populateComponentsToBind(componentsToBind, users, approvedComponentsByUser,
- /* isVisibleBackgroundUser= */ true);
-
- assertEquals(1, componentsToBind.size());
- assertEquals(1, componentsToBind.get(11).size());
- assertTrue(componentsToBind.get(11).contains(ComponentName.unflattenFromString(
- "pkg1/cmp1")));
- assertThat(service.isComponentEnabledForCurrentProfiles(
- new ComponentName("pkg1", "cmp1"))).isFalse();
- assertThat(service.isComponentEnabledForPackage("pkg1")).isFalse();
- }
-
@Test
public void testOnNullBinding() throws Exception {
Context context = mock(Context.class);
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 6596ee9..a51ce995 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -699,8 +699,8 @@
void assertPowerWakeUp() {
mTestLooper.dispatchAll();
- verify(mWindowWakeUpPolicy)
- .wakeUpFromKey(anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
+ verify(mWindowWakeUpPolicy).wakeUpFromKey(
+ eq(DEFAULT_DISPLAY), anyLong(), eq(KeyEvent.KEYCODE_POWER), anyBoolean());
}
void assertNoPowerSleep() {
diff --git a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
index 7322e5a..3ca352c 100644
--- a/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/WindowWakeUpPolicyTests.java
@@ -22,6 +22,7 @@
import static android.os.PowerManager.WAKE_REASON_POWER_BUTTON;
import static android.os.PowerManager.WAKE_REASON_WAKE_KEY;
import static android.os.PowerManager.WAKE_REASON_WAKE_MOTION;
+import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InputDevice.SOURCE_ROTARY_ENCODER;
import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.KeyEvent.KEYCODE_HOME;
@@ -35,6 +36,7 @@
import static com.android.internal.R.bool.config_allowTheaterModeWakeFromLidSwitch;
import static com.android.internal.R.bool.config_allowTheaterModeWakeFromGesture;
import static com.android.server.policy.Flags.FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE;
+import static com.android.server.power.feature.flags.Flags.FLAG_PER_DISPLAY_WAKE_BY_TOUCH;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -43,6 +45,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -52,6 +55,8 @@
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.os.PowerManager;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.view.Display;
@@ -125,6 +130,7 @@
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testMotionWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
setTheaterModeEnabled(false);
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
@@ -136,7 +142,8 @@
// Verify the policy wake up call succeeds because of the call on the delegate, and not
// because of a PowerManager wake up.
- assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isTrue();
+ assertThat(mPolicy.wakeUpFromMotion(
+ mDefaultDisplay.getDisplayId(), 200, SOURCE_TOUCHSCREEN, true)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true);
verifyNoPowerManagerWakeUp();
@@ -144,12 +151,14 @@
// Verify the policy wake up call succeeds because of the PowerManager wake up, since the
// delegate would not handle the wake up request.
- assertThat(mPolicy.wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false)).isTrue();
+ assertThat(mPolicy.wakeUpFromMotion(
+ mDefaultDisplay.getDisplayId(), 300, SOURCE_ROTARY_ENCODER, false)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromMotion(300, SOURCE_ROTARY_ENCODER, false);
verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testKeyWakeUpDelegation_wakePowerManagerIfDelegateDoesNotHandleWake() {
setTheaterModeEnabled(false);
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
@@ -161,7 +170,7 @@
// Verify the policy wake up call succeeds because of the call on the delegate, and not
// because of a PowerManager wake up.
- assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isTrue();
+ assertThat(mPolicy.wakeUpFromKey(DEFAULT_DISPLAY, 200, KEYCODE_POWER, true)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromKey(200, KEYCODE_POWER, true);
verifyNoPowerManagerWakeUp();
@@ -169,7 +178,8 @@
// Verify the policy wake up call succeeds because of the PowerManager wake up, since the
// delegate would not handle the wake up request.
- assertThat(mPolicy.wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false)).isTrue();
+ assertThat(mPolicy.wakeUpFromKey(
+ DEFAULT_DISPLAY, 300, KEYCODE_STEM_PRIMARY, false)).isTrue();
verify(mInputWakeUpDelegate).wakeUpFromKey(300, KEYCODE_STEM_PRIMARY, false);
verify(mPowerManager).wakeUp(300, WAKE_REASON_WAKE_KEY, "android.policy:KEY");
}
@@ -186,7 +196,7 @@
.setInputWakeUpDelegate(mInputWakeUpDelegate);
// Check that the wake up does not happen because the theater mode policy check fails.
- assertThat(mPolicy.wakeUpFromKey(200, KEYCODE_POWER, true)).isFalse();
+ assertThat(mPolicy.wakeUpFromKey(DEFAULT_DISPLAY, 200, KEYCODE_POWER, true)).isFalse();
verify(mInputWakeUpDelegate, never()).wakeUpFromKey(anyLong(), anyInt(), anyBoolean());
}
@@ -201,11 +211,13 @@
.setInputWakeUpDelegate(mInputWakeUpDelegate);
// Check that the wake up does not happen because the theater mode policy check fails.
- assertThat(mPolicy.wakeUpFromMotion(200, SOURCE_TOUCHSCREEN, true)).isFalse();
+ assertThat(mPolicy.wakeUpFromMotion(
+ mDefaultDisplay.getDisplayId(), 200, SOURCE_TOUCHSCREEN, true)).isFalse();
verify(mInputWakeUpDelegate, never()).wakeUpFromMotion(anyLong(), anyInt(), anyBoolean());
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testTheaterModeChecksNotAppliedWhenScreenIsOn() {
mSetFlagsRule.enableFlags(FLAG_SUPPORT_INPUT_WAKEUP_DELEGATE);
setDefaultDisplayState(Display.STATE_ON);
@@ -213,30 +225,69 @@
setBooleanRes(config_allowTheaterModeWakeFromMotion, false);
mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
- mPolicy.wakeUpFromMotion(200L, SOURCE_TOUCHSCREEN, true);
+ mPolicy.wakeUpFromMotion(mDefaultDisplay.getDisplayId(), 200L, SOURCE_TOUCHSCREEN, true);
verify(mPowerManager).wakeUp(200L, WAKE_REASON_WAKE_MOTION, "android.policy:MOTION");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromMotion() {
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromMotion(mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
+ () -> mPolicy.wakeUpFromMotion(mDefaultDisplay.getDisplayId(),
+ mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, true),
config_allowTheaterModeWakeFromMotion,
WAKE_REASON_WAKE_MOTION,
"android.policy:MOTION");
}
@Test
+ @EnableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+ public void testWakeUpFromMotion_perDisplayWakeByTouchEnabled() {
+ setTheaterModeEnabled(false);
+ final int displayId = 555;
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ boolean displayWokeUp = mPolicy.wakeUpFromMotion(
+ displayId, mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, /* isDown= */ true);
+
+ // Verify that display is woken up
+ assertThat(displayWokeUp).isTrue();
+ verify(mPowerManager).wakeUp(anyLong(), eq(WAKE_REASON_WAKE_MOTION),
+ eq("android.policy:MOTION"), eq(displayId));
+ }
+
+ @Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+ public void testWakeUpFromMotion_perDisplayWakeByTouchDisabled() {
+ setTheaterModeEnabled(false);
+ final int displayId = 555;
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ boolean displayWokeUp = mPolicy.wakeUpFromMotion(
+ displayId, mClock.uptimeMillis(), SOURCE_TOUCHSCREEN, /* isDown= */ true);
+
+ // Verify that power is woken up and display isn't woken up individually
+ assertThat(displayWokeUp).isTrue();
+ verify(mPowerManager).wakeUp(
+ anyLong(), eq(WAKE_REASON_WAKE_MOTION), eq("android.policy:MOTION"));
+ verify(mPowerManager, never()).wakeUp(anyLong(), eq(WAKE_REASON_WAKE_MOTION),
+ eq("android.policy:MOTION"), eq(displayId));
+ }
+
+ @Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromKey_nonPowerKey() {
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_HOME, true),
+ () -> mPolicy.wakeUpFromKey(
+ DEFAULT_DISPLAY, mClock.uptimeMillis(), KEYCODE_HOME, true),
config_allowTheaterModeWakeFromKey,
WAKE_REASON_WAKE_KEY,
"android.policy:KEY");
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromKey_powerKey() {
// Disable the resource affecting all wake keys because it affects power key as well.
// That way, power key wake during theater mode will solely be controlled by
@@ -245,7 +296,8 @@
// Test with power key
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, true),
+ () -> mPolicy.wakeUpFromKey(
+ DEFAULT_DISPLAY, mClock.uptimeMillis(), KEYCODE_POWER, true),
config_allowTheaterModeWakeFromPowerKey,
WAKE_REASON_POWER_BUTTON,
"android.policy:POWER");
@@ -254,13 +306,31 @@
// even if the power-key specific theater mode config is disabled.
setBooleanRes(config_allowTheaterModeWakeFromPowerKey, false);
runPowerManagerUpChecks(
- () -> mPolicy.wakeUpFromKey(mClock.uptimeMillis(), KEYCODE_POWER, false),
+ () -> mPolicy.wakeUpFromKey(
+ DEFAULT_DISPLAY, mClock.uptimeMillis(), KEYCODE_POWER, false),
config_allowTheaterModeWakeFromKey,
WAKE_REASON_POWER_BUTTON,
"android.policy:POWER");
}
@Test
+ @EnableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
+ public void testWakeUpFromKey_invalidDisplay_perDisplayWakeByTouchEnabled() {
+ setTheaterModeEnabled(false);
+ final int displayId = Display.INVALID_DISPLAY;
+ mPolicy = new WindowWakeUpPolicy(mContextSpy, mClock);
+
+ boolean displayWokeUp = mPolicy.wakeUpFromKey(
+ displayId, mClock.uptimeMillis(), KEYCODE_POWER, /* isDown= */ false);
+
+ // Verify that default display is woken up
+ assertThat(displayWokeUp).isTrue();
+ verify(mPowerManager).wakeUp(anyLong(), eq(WAKE_REASON_POWER_BUTTON),
+ eq("android.policy:POWER"), eq(DEFAULT_DISPLAY));
+ }
+
+ @Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromLid() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromLid(),
@@ -270,6 +340,7 @@
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromWakeGesture() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromWakeGesture(),
@@ -279,6 +350,7 @@
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testwakeUpFromCameraCover() {
runPowerManagerUpChecks(
() -> mPolicy.wakeUpFromCameraCover(mClock.uptimeMillis()),
@@ -288,6 +360,7 @@
}
@Test
+ @DisableFlags({FLAG_PER_DISPLAY_WAKE_BY_TOUCH})
public void testWakeUpFromPowerKeyCameraGesture() {
// Disable the resource affecting all wake keys because it affects power key as well.
// That way, power key wake during theater mode will solely be controlled by
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 62cbb02..544bfab 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -886,7 +886,7 @@
/**
* @return true if the ImsService to bind to for the slot id specified was set, false otherwise.
*/
- boolean setBoundImsServiceOverride(int slotIndex, boolean isCarrierService,
+ boolean setBoundImsServiceOverride(int slotIndex, int userId, boolean isCarrierService,
in int[] featureTypes, in String packageName);
/**
diff --git a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
index 332b9b8..9a9a331 100644
--- a/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
+++ b/tests/FlickerTests/test-apps/app-helpers/src/com/android/server/wm/flicker/helpers/DesktopModeAppHelper.kt
@@ -21,7 +21,7 @@
import android.graphics.Rect
import android.graphics.Region
import android.os.SystemClock
-import android.platform.uiautomator_helpers.DeviceHelpers
+import android.platform.uiautomatorhelpers.DeviceHelpers
import android.tools.device.apphelpers.IStandardAppHelper
import android.tools.helpers.SYSTEMUI_PACKAGE
import android.tools.traces.parsers.WindowManagerStateHelper
@@ -159,7 +159,7 @@
) {
val caption = getCaptionForTheApp(wmHelper, device)
val maximizeButton = getMaximizeButtonForTheApp(caption)
- maximizeButton?.longClick()
+ maximizeButton.longClick()
wmHelper.StateSyncBuilder().withAppTransitionIdle().waitForAndVerify()
val buttonResId = if (toLeft) SNAP_LEFT_BUTTON else SNAP_RIGHT_BUTTON
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
index f317939c..61400ed 100644
--- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -757,7 +757,31 @@
intArrayOf(KeyEvent.KEYCODE_MINUS),
KeyEvent.META_ALT_ON,
intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
- )
+ ),
+ TestData(
+ "META + ALT + '-' -> Magnifier Zoom Out",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_MINUS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_OUT,
+ intArrayOf(KeyEvent.KEYCODE_MINUS),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
+ TestData(
+ "META + ALT + '=' -> Magnifier Zoom In",
+ intArrayOf(
+ KeyEvent.KEYCODE_META_LEFT,
+ KeyEvent.KEYCODE_ALT_LEFT,
+ KeyEvent.KEYCODE_EQUALS
+ ),
+ KeyGestureEvent.KEY_GESTURE_TYPE_MAGNIFIER_ZOOM_IN,
+ intArrayOf(KeyEvent.KEYCODE_EQUALS),
+ KeyEvent.META_META_ON or KeyEvent.META_ALT_ON,
+ intArrayOf(KeyGestureEvent.ACTION_GESTURE_COMPLETE)
+ ),
)
}
@@ -770,6 +794,7 @@
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+ com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)
@@ -787,6 +812,7 @@
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_SLOW_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG,
com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_MOUSE_KEYS,
+ com.android.hardware.input.Flags.FLAG_ENABLE_TALKBACK_AND_MAGNIFIER_KEY_GESTURES,
com.android.window.flags.Flags.FLAG_ENABLE_MOVE_TO_NEXT_DISPLAY_SHORTCUT,
com.android.window.flags.Flags.FLAG_ENABLE_TASK_RESIZING_KEYBOARD_SHORTCUTS
)