Merge "Don't do the SAW perm check if there's an invalid UID" into rvc-dev
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index 49b3ec1..cea7fcc 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -212,7 +212,10 @@
}
boolean isAccessAllowedForCaller(@NonNull String callingPackage, int callingUid) {
- // TODO: verify blob is still valid (expiryTime is not elapsed)
+ // Don't allow the blob to be accessed after it's expiry time has passed.
+ if (getBlobHandle().isExpired()) {
+ return false;
+ }
synchronized (mMetadataLock) {
// Check if packageName already holds a lease on the blob.
for (int i = 0, size = mLeasees.size(); i < size; ++i) {
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 7a27b2c..a2bce31 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -1059,6 +1059,18 @@
}
}
+ boolean isBlobAvailable(long blobId, int userId) {
+ synchronized (mBlobsLock) {
+ final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
+ for (BlobMetadata blobMetadata : userBlobs.values()) {
+ if (blobMetadata.getBlobId() == blobId) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
@GuardedBy("mBlobsLock")
private void dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
index 72af323..a4a2e80 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerShellCommand.java
@@ -46,6 +46,8 @@
return runDeleteBlob(pw);
case "idle-maintenance":
return runIdleMaintenance(pw);
+ case "query-blob-existence":
+ return runQueryBlobExistence(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -91,6 +93,16 @@
return 0;
}
+ private int runQueryBlobExistence(PrintWriter pw) {
+ final ParsedArgs args = new ParsedArgs();
+ if (parseOptions(pw, args) < 0) {
+ return -1;
+ }
+
+ pw.println(mService.isBlobAvailable(args.blobId, args.userId) ? 1 : 0);
+ return 0;
+ }
+
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
@@ -121,6 +133,8 @@
pw.println(" --tag: Tag of the blob to delete.");
pw.println("idle-maintenance");
pw.println(" Run idle maintenance which takes care of removing stale data.");
+ pw.println("query-blob-existence [-b BLOB_ID]");
+ pw.println(" Prints 1 if blob exists, otherwise 0.");
pw.println();
}
@@ -147,6 +161,9 @@
case "--tag":
args.tag = getNextArgRequired();
break;
+ case "-b":
+ args.blobId = Long.parseLong(getNextArgRequired());
+ break;
default:
pw.println("Error: unknown option '" + opt + "'");
return -1;
@@ -166,6 +183,7 @@
public long expiryTimeMillis;
public CharSequence label;
public String tag;
+ public long blobId;
public BlobHandle getBlobHandle() {
return BlobHandle.create(algorithm, digest, label, expiryTimeMillis, tag);
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 4ffa040..47bab29 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1298,7 +1298,6 @@
return Status::ok();
}
-
void StatsService::statsCompanionServiceDied(void* cookie) {
auto thiz = static_cast<StatsService*>(cookie);
thiz->statsCompanionServiceDiedImpl();
diff --git a/cmds/statsd/src/shell/ShellSubscriber.cpp b/cmds/statsd/src/shell/ShellSubscriber.cpp
index 361b161..fd883c2 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.cpp
+++ b/cmds/statsd/src/shell/ShellSubscriber.cpp
@@ -41,13 +41,8 @@
{
std::unique_lock<std::mutex> lock(mMutex);
- if (myToken != mToken) {
- // Some other subscription has already come in. Stop.
- return;
- }
mSubscriptionInfo = mySubscriptionInfo;
-
- spawnHelperThreadsLocked(mySubscriptionInfo, myToken);
+ spawnHelperThread(myToken);
waitForSubscriptionToEndLocked(mySubscriptionInfo, myToken, lock, timeoutSec);
if (mSubscriptionInfo == mySubscriptionInfo) {
@@ -57,14 +52,9 @@
}
}
-void ShellSubscriber::spawnHelperThreadsLocked(shared_ptr<SubscriptionInfo> myInfo, int myToken) {
- if (!myInfo->mPulledInfo.empty() && myInfo->mPullIntervalMin > 0) {
- std::thread puller([this, myToken] { startPull(myToken); });
- puller.detach();
- }
-
- std::thread heartbeatSender([this, myToken] { sendHeartbeats(myToken); });
- heartbeatSender.detach();
+void ShellSubscriber::spawnHelperThread(int myToken) {
+ std::thread t([this, myToken] { pullAndSendHeartbeats(myToken); });
+ t.detach();
}
void ShellSubscriber::waitForSubscriptionToEndLocked(shared_ptr<SubscriptionInfo> myInfo,
@@ -114,13 +104,7 @@
subscriptionInfo->mPushedMatchers.push_back(pushed);
}
- int minInterval = -1;
for (const auto& pulled : config.pulled()) {
- // All intervals need to be multiples of the min interval.
- if (minInterval < 0 || pulled.freq_millis() < minInterval) {
- minInterval = pulled.freq_millis();
- }
-
vector<string> packages;
vector<int32_t> uids;
for (const string& pkg : pulled.packages()) {
@@ -136,18 +120,18 @@
uids);
VLOG("adding matcher for pulled atom %d", pulled.matcher().atom_id());
}
- subscriptionInfo->mPullIntervalMin = minInterval;
return true;
}
-void ShellSubscriber::startPull(int myToken) {
- VLOG("ShellSubscriber: pull thread %d starting", myToken);
+void ShellSubscriber::pullAndSendHeartbeats(int myToken) {
+ VLOG("ShellSubscriber: helper thread %d starting", myToken);
while (true) {
+ int64_t sleepTimeMs = INT_MAX;
{
std::lock_guard<std::mutex> lock(mMutex);
if (!mSubscriptionInfo || mToken != myToken) {
- VLOG("ShellSubscriber: pulling thread %d done!", myToken);
+ VLOG("ShellSubscriber: helper thread %d done!", myToken);
return;
}
@@ -168,11 +152,27 @@
pullInfo.mPrevPullElapsedRealtimeMs = nowMillis;
}
+
+ // Send a heartbeat, consisting of a data size of 0, if perfd hasn't recently received
+ // data from statsd. When it receives the data size of 0, perfd will not expect any
+ // atoms and recheck whether the subscription should end.
+ if (nowMillis - mLastWriteMs > kMsBetweenHeartbeats) {
+ attemptWriteToPipeLocked(/*dataSize=*/0);
+ }
+
+ // Determine how long to sleep before doing more work.
+ for (PullInfo& pullInfo : mSubscriptionInfo->mPulledInfo) {
+ int64_t nextPullTime = pullInfo.mPrevPullElapsedRealtimeMs + pullInfo.mInterval;
+ int64_t timeBeforePull = nextPullTime - nowMillis; // guaranteed to be non-negative
+ if (timeBeforePull < sleepTimeMs) sleepTimeMs = timeBeforePull;
+ }
+ int64_t timeBeforeHeartbeat = (mLastWriteMs + kMsBetweenHeartbeats) - nowMillis;
+ if (timeBeforeHeartbeat < sleepTimeMs) sleepTimeMs = timeBeforeHeartbeat;
}
- VLOG("ShellSubscriber: pulling thread %d sleeping for %d ms", myToken,
- mSubscriptionInfo->mPullIntervalMin);
- std::this_thread::sleep_for(std::chrono::milliseconds(mSubscriptionInfo->mPullIntervalMin));
+ VLOG("ShellSubscriber: helper thread %d sleeping for %lld ms", myToken,
+ (long long)sleepTimeMs);
+ std::this_thread::sleep_for(std::chrono::milliseconds(sleepTimeMs));
}
}
@@ -200,7 +200,7 @@
}
}
- if (count > 0) attemptWriteToSocketLocked(mProto.size());
+ if (count > 0) attemptWriteToPipeLocked(mProto.size());
}
void ShellSubscriber::onLogEvent(const LogEvent& event) {
@@ -214,26 +214,24 @@
util::FIELD_COUNT_REPEATED | FIELD_ID_ATOM);
event.ToProto(mProto);
mProto.end(atomToken);
- attemptWriteToSocketLocked(mProto.size());
+ attemptWriteToPipeLocked(mProto.size());
}
}
}
-// Tries to write the atom encoded in mProto to the socket. If the write fails
+// Tries to write the atom encoded in mProto to the pipe. If the write fails
// because the read end of the pipe has closed, signals to other threads that
// the subscription should end.
-void ShellSubscriber::attemptWriteToSocketLocked(size_t dataSize) {
- // First write the payload size.
+void ShellSubscriber::attemptWriteToPipeLocked(size_t dataSize) {
+ // First, write the payload size.
if (!android::base::WriteFully(mSubscriptionInfo->mOutputFd, &dataSize, sizeof(dataSize))) {
mSubscriptionInfo->mClientAlive = false;
mSubscriptionShouldEnd.notify_one();
return;
}
- if (dataSize == 0) return;
-
- // Then, write the payload.
- if (!mProto.flush(mSubscriptionInfo->mOutputFd)) {
+ // Then, write the payload if this is not just a heartbeat.
+ if (dataSize > 0 && !mProto.flush(mSubscriptionInfo->mOutputFd)) {
mSubscriptionInfo->mClientAlive = false;
mSubscriptionShouldEnd.notify_one();
return;
@@ -242,28 +240,6 @@
mLastWriteMs = getElapsedRealtimeMillis();
}
-// Send a heartbeat, consisting solely of a data size of 0, if perfd has not
-// recently received any writes from statsd. When it receives the data size of
-// 0, perfd will not expect any data and recheck whether the shell command is
-// still running.
-void ShellSubscriber::sendHeartbeats(int myToken) {
- while (true) {
- {
- std::lock_guard<std::mutex> lock(mMutex);
- if (!mSubscriptionInfo || myToken != mToken) {
- VLOG("ShellSubscriber: heartbeat thread %d done!", myToken);
- return;
- }
-
- if (getElapsedRealtimeMillis() - mLastWriteMs > kMsBetweenHeartbeats) {
- VLOG("ShellSubscriber: sending a heartbeat to perfd");
- attemptWriteToSocketLocked(/*dataSize=*/0);
- }
- }
- std::this_thread::sleep_for(std::chrono::milliseconds(kMsBetweenHeartbeats));
- }
-}
-
} // namespace statsd
} // namespace os
} // namespace android
diff --git a/cmds/statsd/src/shell/ShellSubscriber.h b/cmds/statsd/src/shell/ShellSubscriber.h
index 26c8a2a..4c05fa7 100644
--- a/cmds/statsd/src/shell/ShellSubscriber.h
+++ b/cmds/statsd/src/shell/ShellSubscriber.h
@@ -92,7 +92,6 @@
int mOutputFd;
std::vector<SimpleAtomMatcher> mPushedMatchers;
std::vector<PullInfo> mPulledInfo;
- int mPullIntervalMin;
bool mClientAlive;
};
@@ -100,27 +99,25 @@
bool readConfig(std::shared_ptr<SubscriptionInfo> subscriptionInfo);
- void spawnHelperThreadsLocked(std::shared_ptr<SubscriptionInfo> myInfo, int myToken);
+ void spawnHelperThread(int myToken);
void waitForSubscriptionToEndLocked(std::shared_ptr<SubscriptionInfo> myInfo,
int myToken,
std::unique_lock<std::mutex>& lock,
int timeoutSec);
- void startPull(int myToken);
+ // Helper thread that pulls atoms at a regular frequency and sends
+ // heartbeats to perfd if statsd hasn't recently sent any data. Statsd must
+ // send heartbeats for perfd to escape a blocking read call and recheck if
+ // the user has terminated the subscription.
+ void pullAndSendHeartbeats(int myToken);
void writePulledAtomsLocked(const vector<std::shared_ptr<LogEvent>>& data,
const SimpleAtomMatcher& matcher);
void getUidsForPullAtom(vector<int32_t>* uids, const PullInfo& pullInfo);
- void attemptWriteToSocketLocked(size_t dataSize);
-
- // Send ocassional heartbeats for two reasons: (a) for statsd to detect when
- // the read end of the pipe has closed and (b) for perfd to escape a
- // blocking read call and recheck if the user has terminated the
- // subscription.
- void sendHeartbeats(int myToken);
+ void attemptWriteToPipeLocked(size_t dataSize);
sp<UidMap> mUidMap;
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 90206b6..e8ce92d 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -3614,6 +3614,7 @@
* <li>Directional conversations where there is an active speaker and many passive
* individuals</li>
* <li>Stream / posting updates from other individuals</li>
+ * <li>Email, document comments, or other conversation types that are not real-time</li>
* </ul>
* </p>
*
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 01cf2b94a..5806876 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -188,6 +188,17 @@
private static final boolean DEBUG = false;
private static final boolean VERIFY = false;
+ // Per-Cache performance counters. As some cache instances are declared static,
+ @GuardedBy("mLock")
+ private long mHits = 0;
+
+ @GuardedBy("mLock")
+ private long mMisses = 0;
+
+ // Most invalidation is done in a static context, so the counters need to be accessible.
+ @GuardedBy("sCorkLock")
+ private static final HashMap<String, Long> sInvalidates = new HashMap<>();
+
/**
* If sEnabled is false then all cache operations are stubbed out. Set
* it to false inside test processes.
@@ -265,6 +276,7 @@
};
synchronized (sCorkLock) {
sCaches.put(this, null);
+ sInvalidates.put(propertyName, (long) 0);
}
}
@@ -365,6 +377,8 @@
synchronized (mLock) {
if (currentNonce == mLastSeenNonce) {
cachedResult = mCache.get(query);
+
+ if (cachedResult != null) mHits++;
} else {
if (DEBUG) {
Log.d(TAG,
@@ -428,6 +442,7 @@
if (mLastSeenNonce == currentNonce && result != null) {
mCache.put(query, result);
}
+ mMisses++;
}
return maybeCheckConsistency(query, result);
}
@@ -531,6 +546,8 @@
newValueString));
}
SystemProperties.set(name, newValueString);
+ long invalidateCount = sInvalidates.getOrDefault(name, (long) 0);
+ sInvalidates.put(name, ++invalidateCount);
}
/**
@@ -758,8 +775,16 @@
}
private void dumpContents(PrintWriter pw, String[] args) {
+ long invalidateCount;
+
+ synchronized (sCorkLock) {
+ invalidateCount = sInvalidates.getOrDefault(mPropertyName, (long) 0);
+ }
+
synchronized (mLock) {
pw.println(String.format(" Cache Property Name: %s", cacheName()));
+ pw.println(String.format(" Hits: %d, Misses: %d, Invalidates: %d",
+ mHits, mMisses, invalidateCount));
pw.println(String.format(" Last Observed Nonce: %d", mLastSeenNonce));
pw.println(String.format(" Current Size: %d, Max Size: %d",
mCache.entrySet().size(), mMaxEntries));
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index aa29040..389458b 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -99,9 +99,9 @@
in IShortcutChangeCallback callback);
void cacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
- in UserHandle user);
+ in UserHandle user, int cacheFlags);
void uncacheShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
- in UserHandle user);
+ in UserHandle user, int cacheFlags);
String getShortcutIconUri(String callingPackage, String packageName, String shortcutId,
int userId);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 4299e80..bd1ee27 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -155,6 +155,26 @@
public static final String EXTRA_PIN_ITEM_REQUEST =
"android.content.pm.extra.PIN_ITEM_REQUEST";
+ /**
+ * Cache shortcuts which are used in notifications.
+ * @hide
+ */
+ public static final int FLAG_CACHE_NOTIFICATION_SHORTCUTS = 0;
+
+ /**
+ * Cache shortcuts which are used in bubbles.
+ * @hide
+ */
+ public static final int FLAG_CACHE_BUBBLE_SHORTCUTS = 1;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = { "FLAG_CACHE_" }, value = {
+ FLAG_CACHE_NOTIFICATION_SHORTCUTS,
+ FLAG_CACHE_BUBBLE_SHORTCUTS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ShortcutCacheFlags {}
+
private final Context mContext;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private final ILauncherApps mService;
@@ -1109,6 +1129,11 @@
* @param packageName The target package name.
* @param shortcutIds The IDs of the shortcut to be cached.
* @param user The UserHandle of the profile.
+ * @param cacheFlags One of the values in:
+ * <ul>
+ * <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
+ * <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
+ * </ul>
* @throws IllegalStateException when the user is locked, or when the {@code user} user
* is locked or not running.
*
@@ -1118,10 +1143,11 @@
*/
@RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS)
public void cacheShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
- @NonNull UserHandle user) {
+ @NonNull UserHandle user, @ShortcutCacheFlags int cacheFlags) {
logErrorForInvalidProfileAccess(user);
try {
- mService.cacheShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
+ mService.cacheShortcuts(
+ mContext.getPackageName(), packageName, shortcutIds, user, cacheFlags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -1133,6 +1159,11 @@
* @param packageName The target package name.
* @param shortcutIds The IDs of the shortcut to be uncached.
* @param user The UserHandle of the profile.
+ * @param cacheFlags One of the values in:
+ * <ul>
+ * <li>{@link #FLAG_CACHE_NOTIFICATION_SHORTCUTS}
+ * <li>{@link #FLAG_CACHE_BUBBLE_SHORTCUTS}
+ * </ul>
* @throws IllegalStateException when the user is locked, or when the {@code user} user
* is locked or not running.
*
@@ -1142,10 +1173,11 @@
*/
@RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS)
public void uncacheShortcuts(@NonNull String packageName, @NonNull List<String> shortcutIds,
- @NonNull UserHandle user) {
+ @NonNull UserHandle user, @ShortcutCacheFlags int cacheFlags) {
logErrorForInvalidProfileAccess(user);
try {
- mService.uncacheShortcuts(mContext.getPackageName(), packageName, shortcutIds, user);
+ mService.uncacheShortcuts(
+ mContext.getPackageName(), packageName, shortcutIds, user, cacheFlags);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java
index dcc6cb2..1b3c46f 100644
--- a/core/java/android/content/pm/ShortcutInfo.java
+++ b/core/java/android/content/pm/ShortcutInfo.java
@@ -119,12 +119,27 @@
/** @hide */
public static final int FLAG_LONG_LIVED = 1 << 13;
- /** @hide */
- public static final int FLAG_CACHED = 1 << 14;
+ /**
+ * TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
+ * need to be aware of the outside world. Replace this with a more extensible solution.
+ * @hide
+ */
+ public static final int FLAG_CACHED_NOTIFICATIONS = 1 << 14;
/** @hide */
public static final int FLAG_HAS_ICON_URI = 1 << 15;
+
+ /**
+ * TODO(b/155135057): This is a quick and temporary fix for b/155135890. ShortcutService doesn't
+ * need to be aware of the outside world. Replace this with a more extensible solution.
+ * @hide
+ */
+ public static final int FLAG_CACHED_BUBBLES = 1 << 30;
+
+ /** @hide */
+ public static final int FLAG_CACHED_ALL = FLAG_CACHED_NOTIFICATIONS | FLAG_CACHED_BUBBLES;
+
/** @hide */
@IntDef(flag = true, prefix = { "FLAG_" }, value = {
FLAG_DYNAMIC,
@@ -141,8 +156,9 @@
FLAG_ICON_FILE_PENDING_SAVE,
FLAG_SHADOW,
FLAG_LONG_LIVED,
- FLAG_CACHED,
FLAG_HAS_ICON_URI,
+ FLAG_CACHED_NOTIFICATIONS,
+ FLAG_CACHED_BUBBLES,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ShortcutFlags {}
@@ -1707,13 +1723,13 @@
}
/** @hide */
- public void setCached() {
- addFlags(FLAG_CACHED);
+ public void setCached(@ShortcutFlags int cacheFlag) {
+ addFlags(cacheFlag);
}
/** Return whether a shortcut is cached. */
public boolean isCached() {
- return hasFlags(FLAG_CACHED);
+ return (getFlags() & FLAG_CACHED_ALL) != 0;
}
/** Return whether a shortcut is dynamic. */
@@ -1807,7 +1823,7 @@
/** @hide */
public boolean isAlive() {
return hasFlags(FLAG_PINNED) || hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST)
- || hasFlags(FLAG_CACHED);
+ || isCached();
}
/** @hide */
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index eee91ce1..c62767e 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -92,10 +92,10 @@
public abstract void cacheShortcuts(int launcherUserId,
@NonNull String callingPackage, @NonNull String packageName,
- @NonNull List<String> shortcutIds, int userId);
+ @NonNull List<String> shortcutIds, int userId, int cacheFlags);
public abstract void uncacheShortcuts(int launcherUserId,
@NonNull String callingPackage, @NonNull String packageName,
- @NonNull List<String> shortcutIds, int userId);
+ @NonNull List<String> shortcutIds, int userId, int cacheFlags);
/**
* Retrieves all of the direct share targets that match the given IntentFilter for the specified
diff --git a/core/java/android/net/Ikev2VpnProfile.java b/core/java/android/net/Ikev2VpnProfile.java
index 836624b..407ff04 100644
--- a/core/java/android/net/Ikev2VpnProfile.java
+++ b/core/java/android/net/Ikev2VpnProfile.java
@@ -101,6 +101,7 @@
private final boolean mIsBypassable; // Defaults in builder
private final boolean mIsMetered; // Defaults in builder
private final int mMaxMtu; // Defaults in builder
+ private final boolean mIsRestrictedToTestNetworks;
private Ikev2VpnProfile(
int type,
@@ -116,7 +117,8 @@
@NonNull List<String> allowedAlgorithms,
boolean isBypassable,
boolean isMetered,
- int maxMtu) {
+ int maxMtu,
+ boolean restrictToTestNetworks) {
super(type);
checkNotNull(serverAddr, MISSING_PARAM_MSG_TMPL, "Server address");
@@ -140,6 +142,7 @@
mIsBypassable = isBypassable;
mIsMetered = isMetered;
mMaxMtu = maxMtu;
+ mIsRestrictedToTestNetworks = restrictToTestNetworks;
validate();
}
@@ -329,6 +332,15 @@
return mMaxMtu;
}
+ /**
+ * Returns whether or not this VPN profile is restricted to test networks.
+ *
+ * @hide
+ */
+ public boolean isRestrictedToTestNetworks() {
+ return mIsRestrictedToTestNetworks;
+ }
+
@Override
public int hashCode() {
return Objects.hash(
@@ -345,7 +357,8 @@
mAllowedAlgorithms,
mIsBypassable,
mIsMetered,
- mMaxMtu);
+ mMaxMtu,
+ mIsRestrictedToTestNetworks);
}
@Override
@@ -368,7 +381,8 @@
&& Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms)
&& mIsBypassable == other.mIsBypassable
&& mIsMetered == other.mIsMetered
- && mMaxMtu == other.mMaxMtu;
+ && mMaxMtu == other.mMaxMtu
+ && mIsRestrictedToTestNetworks == other.mIsRestrictedToTestNetworks;
}
/**
@@ -381,7 +395,8 @@
*/
@NonNull
public VpnProfile toVpnProfile() throws IOException, GeneralSecurityException {
- final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */);
+ final VpnProfile profile = new VpnProfile("" /* Key; value unused by IKEv2VpnProfile(s) */,
+ mIsRestrictedToTestNetworks);
profile.type = mType;
profile.server = mServerAddr;
profile.ipsecIdentifier = mUserIdentity;
@@ -449,6 +464,9 @@
builder.setBypassable(profile.isBypassable);
builder.setMetered(profile.isMetered);
builder.setMaxMtu(profile.maxMtu);
+ if (profile.isRestrictedToTestNetworks) {
+ builder.restrictToTestNetworks();
+ }
switch (profile.type) {
case TYPE_IKEV2_IPSEC_USER_PASS:
@@ -621,6 +639,7 @@
private boolean mIsBypassable = false;
private boolean mIsMetered = true;
private int mMaxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT;
+ private boolean mIsRestrictedToTestNetworks = false;
/**
* Creates a new builder with the basic parameters of an IKEv2/IPsec VPN.
@@ -842,6 +861,21 @@
}
/**
+ * Restricts this profile to use test networks (only).
+ *
+ * <p>This method is for testing only, and must not be used by apps. Calling
+ * provisionVpnProfile() with a profile where test-network usage is enabled will require the
+ * MANAGE_TEST_NETWORKS permission.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder restrictToTestNetworks() {
+ mIsRestrictedToTestNetworks = true;
+ return this;
+ }
+
+ /**
* Validates, builds and provisions the VpnProfile.
*
* @throws IllegalArgumentException if any of the required keys or values were invalid
@@ -862,7 +896,8 @@
mAllowedAlgorithms,
mIsBypassable,
mIsMetered,
- mMaxMtu);
+ mMaxMtu,
+ mIsRestrictedToTestNetworks);
}
}
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 3d95f65..ef9edc6c 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -70,6 +70,8 @@
*/
public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks {
+ private int mTypesBeingCancelled;
+
public interface Host {
Handler getHandler();
@@ -743,6 +745,10 @@
consumer.getType(), animationType, consumer.isRequestedVisible()));
continue;
}
+ if (fromIme && animationType == ANIMATION_TYPE_USER) {
+ // App is already controlling the IME, don't cancel it.
+ continue;
+ }
typesReady |= InsetsState.toPublicType(consumer.getType());
}
if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady);
@@ -809,6 +815,12 @@
@AnimationType int animationType,
@LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation,
boolean useInsetsAnimationThread) {
+ if ((types & mTypesBeingCancelled) != 0) {
+ throw new IllegalStateException("Cannot start a new insets animation of "
+ + Type.toString(types)
+ + " while an existing " + Type.toString(mTypesBeingCancelled)
+ + " is being cancelled.");
+ }
if (types == 0) {
// nothing to animate.
listener.onCancelled(null);
@@ -868,7 +880,9 @@
if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: "
+ useInsetsAnimationThread);
if (cancellationSignal != null) {
- cancellationSignal.setOnCancelListener(runner::cancel);
+ cancellationSignal.setOnCancelListener(() -> {
+ cancelAnimation(runner, true /* invokeCallback */);
+ });
}
if (layoutInsetsDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
showDirectly(types);
@@ -963,14 +977,20 @@
}
private void cancelExistingControllers(@InsetsType int types) {
- for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
- InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
- if ((control.getTypes() & types) != 0) {
- cancelAnimation(control, true /* invokeCallback */);
+ final int originalmTypesBeingCancelled = mTypesBeingCancelled;
+ mTypesBeingCancelled |= types;
+ try {
+ for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+ InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
+ if ((control.getTypes() & types) != 0) {
+ cancelAnimation(control, true /* invokeCallback */);
+ }
}
- }
- if ((types & ime()) != 0) {
- abortPendingImeControlRequest();
+ if ((types & ime()) != 0) {
+ abortPendingImeControlRequest();
+ }
+ } finally {
+ mTypesBeingCancelled = originalmTypesBeingCancelled;
}
}
@@ -1029,6 +1049,9 @@
mHost.notifyInsetsChanged();
}
}
+ if (invokeCallback && runningAnimation.startDispatched) {
+ dispatchAnimationEnd(runningAnimation.runner.getAnimation());
+ }
break;
}
}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index 0807f41..7042f29 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -202,9 +202,9 @@
* <p>To suppress the dialog and allow JavaScript execution to
* continue, call {@code JsResult.confirm()} immediately and then return
* {@code true}.
- * <p>Note that if the {@link WebChromeClient} is {@code null}, the default
- * dialog will be suppressed and Javascript execution will continue
- * immediately.
+ * <p>Note that if the {@link WebChromeClient} is set to be {@code null},
+ * or if {@link WebChromeClient} is not set at all, the default dialog will
+ * be suppressed and Javascript execution will continue immediately.
*
* @param view The WebView that initiated the callback.
* @param url The url of the page requesting the dialog.
@@ -236,9 +236,10 @@
* <p>To suppress the dialog and allow JavaScript execution to continue,
* call {@code JsResult.confirm()} or {@code JsResult.cancel()} immediately
* and then return {@code true}.
- * <p>Note that if the {@link WebChromeClient} is {@code null}, the default
- * dialog will be suppressed and the default value of {@code false} will be
- * returned to the JavaScript code immediately.
+ * <p>Note that if the {@link WebChromeClient} is set to be {@code null},
+ * or if {@link WebChromeClient} is not set at all, the default dialog will
+ * be suppressed and the default value of {@code false} will be returned to
+ * the JavaScript code immediately.
*
* @param view The WebView that initiated the callback.
* @param url The url of the page requesting the dialog.
@@ -269,9 +270,10 @@
* <p>To suppress the dialog and allow JavaScript execution to continue,
* call {@code JsPromptResult.confirm(result)} immediately and then
* return {@code true}.
- * <p>Note that if the {@link WebChromeClient} is {@code null}, the default
- * dialog will be suppressed and {@code null} will be returned to the
- * JavaScript code immediately.
+ * <p>Note that if the {@link WebChromeClient} is set to be {@code null},
+ * or if {@link WebChromeClient} is not set at all, the default dialog will
+ * be suppressed and {@code null} will be returned to the JavaScript code
+ * immediately.
*
* @param view The WebView that initiated the callback.
* @param url The url of the page requesting the dialog.
@@ -288,20 +290,32 @@
}
/**
- * Tell the client to display a dialog to confirm navigation away from the
- * current page. This is the result of the onbeforeunload javascript event.
- * If the client returns {@code true}, WebView will assume that the client will
- * handle the confirm dialog and call the appropriate JsResult method. If
- * the client returns {@code false}, a default value of {@code true} will be returned to
- * javascript to accept navigation away from the current page. The default
- * behavior is to return {@code false}. Setting the JsResult to {@code true} will navigate
- * away from the current page, {@code false} will cancel the navigation.
+ * Notify the host application that the web page wants to confirm navigation
+ * from JavaScript {@code onbeforeunload}.
+ * <p>The default behavior if this method returns {@code false} or is not
+ * overridden is to show a dialog containing the message and suspend
+ * JavaScript execution until the dialog is dismissed. The default dialog
+ * will continue the navigation if the user confirms the navigation, and
+ * will stop the navigation if the user wants to stay on the current page.
+ * <p>To show a custom dialog, the app should return {@code true} from this
+ * method, in which case the default dialog will not be shown and JavaScript
+ * execution will be suspended. When the custom dialog is dismissed, the
+ * app should call {@code JsResult.confirm()} to continue the navigation or,
+ * {@code JsResult.cancel()} to stay on the current page.
+ * <p>To suppress the dialog and allow JavaScript execution to continue,
+ * call {@code JsResult.confirm()} or {@code JsResult.cancel()} immediately
+ * and then return {@code true}.
+ * <p>Note that if the {@link WebChromeClient} is set to be {@code null},
+ * or if {@link WebChromeClient} is not set at all, the default dialog will
+ * be suppressed and the navigation will be resumed immediately.
+ *
* @param view The WebView that initiated the callback.
* @param url The url of the page requesting the dialog.
* @param message Message to be displayed in the window.
* @param result A JsResult used to send the user's response to
* javascript.
- * @return boolean Whether the client will handle the confirm dialog.
+ * @return boolean {@code true} if the request is handled or ignored.
+ * {@code false} if WebView needs to show the default dialog.
*/
public boolean onJsBeforeUnload(WebView view, String url, String message,
JsResult result) {
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 8b0dd8a..1f25feb 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -171,17 +171,6 @@
public static final String EXTRA_PRIVATE_RETAIN_IN_ON_STOP
= "com.android.internal.app.ChooserActivity.EXTRA_PRIVATE_RETAIN_IN_ON_STOP";
- /**
- * Integer extra to indicate which profile should be automatically selected.
- * <p>Can only be used if there is a work profile.
- * <p>Possible values can be either {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}.
- */
- static final String EXTRA_SELECTED_PROFILE =
- "com.android.internal.app.ChooserActivity.EXTRA_SELECTED_PROFILE";
-
- static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL;
- static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK;
-
private static final String PREF_NUM_SHEET_EXPANSIONS = "pref_num_sheet_expansions";
private static final String CHIP_LABEL_METADATA_KEY = "android.service.chooser.chip_label";
@@ -928,15 +917,8 @@
}
private int findSelectedProfile() {
- int selectedProfile;
- if (getIntent().hasExtra(EXTRA_SELECTED_PROFILE)) {
- selectedProfile = getIntent().getIntExtra(EXTRA_SELECTED_PROFILE, /* defValue = */ -1);
- if (selectedProfile != PROFILE_PERSONAL && selectedProfile != PROFILE_WORK) {
- throw new IllegalArgumentException(EXTRA_SELECTED_PROFILE + " has invalid value "
- + selectedProfile + ". Must be either ChooserActivity.PROFILE_PERSONAL or "
- + "ChooserActivity.PROFILE_WORK.");
- }
- } else {
+ int selectedProfile = getSelectedProfileExtra();
+ if (selectedProfile == -1) {
selectedProfile = getProfileForUser(getUser());
}
return selectedProfile;
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index fca156a..e65d1fe 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -18,6 +18,8 @@
import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
+import static com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE;
+
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.app.Activity;
@@ -26,6 +28,7 @@
import android.app.AppGlobals;
import android.app.admin.DevicePolicyManager;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -74,6 +77,9 @@
private static final String TEL_SCHEME = "tel";
+ private static final ComponentName RESOLVER_COMPONENT_NAME =
+ new ComponentName("android", ResolverActivity.class.getName());
+
private Injector mInjector;
private MetricsLogger mMetricsLogger;
@@ -136,21 +142,50 @@
}
newIntent.prepareToLeaveUser(callingUserId);
- maybeShowDisclosureAsync(intentReceived, newIntent, targetUserId, userMessageId);
- CompletableFuture.runAsync(() ->
- startActivityAsCaller(newIntent, targetUserId), mExecutorService)
- .thenAcceptAsync(result -> finish(), getApplicationContext().getMainExecutor());
+ final CompletableFuture<ResolveInfo> targetResolveInfoFuture =
+ mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, targetUserId);
+ targetResolveInfoFuture
+ .thenApplyAsync(targetResolveInfo -> {
+ if (isResolverActivityResolveInfo(targetResolveInfo)) {
+ launchResolverActivityWithCorrectTab(intentReceived, className, newIntent,
+ callingUserId, targetUserId);
+ return targetResolveInfo;
+ }
+ startActivityAsCaller(newIntent, targetUserId);
+ return targetResolveInfo;
+ }, mExecutorService)
+ .thenAcceptAsync(result -> {
+ maybeShowDisclosure(intentReceived, result, userMessageId);
+ finish();
+ }, getApplicationContext().getMainExecutor());
}
- private void maybeShowDisclosureAsync(
- Intent intentReceived, Intent newIntent, int userId, int messageId) {
- final CompletableFuture<ResolveInfo> resolveInfoFuture =
- mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, userId);
- resolveInfoFuture.thenAcceptAsync(ri -> {
- if (shouldShowDisclosure(ri, intentReceived)) {
- mInjector.showToast(messageId, Toast.LENGTH_LONG);
- }
- }, getApplicationContext().getMainExecutor());
+ private boolean isIntentForwarderResolveInfo(ResolveInfo resolveInfo) {
+ if (resolveInfo == null) {
+ return false;
+ }
+ ActivityInfo activityInfo = resolveInfo.activityInfo;
+ if (activityInfo == null) {
+ return false;
+ }
+ if (!"android".equals(activityInfo.packageName)) {
+ return false;
+ }
+ return activityInfo.name.equals(FORWARD_INTENT_TO_PARENT)
+ || activityInfo.name.equals(FORWARD_INTENT_TO_MANAGED_PROFILE);
+ }
+
+ private boolean isResolverActivityResolveInfo(@Nullable ResolveInfo resolveInfo) {
+ return resolveInfo != null
+ && resolveInfo.activityInfo != null
+ && RESOLVER_COMPONENT_NAME.equals(resolveInfo.activityInfo.getComponentName());
+ }
+
+ private void maybeShowDisclosure(
+ Intent intentReceived, ResolveInfo resolveInfo, int messageId) {
+ if (shouldShowDisclosure(resolveInfo, intentReceived)) {
+ mInjector.showToast(messageId, Toast.LENGTH_LONG);
+ }
}
private void startActivityAsCaller(Intent newIntent, int userId) {
@@ -185,7 +220,7 @@
// when cross-profile intents are disabled.
int selectedProfile = findSelectedProfile(className);
sanitizeIntent(intentReceived);
- intentReceived.putExtra(ChooserActivity.EXTRA_SELECTED_PROFILE, selectedProfile);
+ intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile);
Intent innerIntent = intentReceived.getParcelableExtra(Intent.EXTRA_INTENT);
if (innerIntent == null) {
Slog.wtf(TAG, "Cannot start a chooser intent with no extra " + Intent.EXTRA_INTENT);
@@ -196,6 +231,25 @@
finish();
}
+ private void launchResolverActivityWithCorrectTab(Intent intentReceived, String className,
+ Intent newIntent, int callingUserId, int targetUserId) {
+ // When showing the intent resolver, instead of forwarding to the other profile,
+ // we launch it in the current user and select the other tab. This fixes b/155874820.
+ //
+ // In the case when there are 0 targets in the current profile and >1 apps in the other
+ // profile, the package manager launches the intent resolver in the other profile.
+ // If that's the case, we launch the resolver in the target user instead (other profile).
+ ResolveInfo callingResolveInfo = mInjector.resolveActivityAsUser(
+ newIntent, MATCH_DEFAULT_ONLY, callingUserId).join();
+ int userId = isIntentForwarderResolveInfo(callingResolveInfo)
+ ? targetUserId : callingUserId;
+ int selectedProfile = findSelectedProfile(className);
+ sanitizeIntent(intentReceived);
+ intentReceived.putExtra(EXTRA_SELECTED_PROFILE, selectedProfile);
+ startActivityAsCaller(intentReceived, null, null, false, userId);
+ finish();
+ }
+
private int findSelectedProfile(String className) {
if (className.equals(FORWARD_INTENT_TO_PARENT)) {
return ChooserActivity.PROFILE_PERSONAL;
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 182c7f2..66d850e 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -179,6 +179,17 @@
// Intent extra for connected audio devices
public static final String EXTRA_IS_AUDIO_CAPTURE_DEVICE = "is_audio_capture_device";
+ /**
+ * Integer extra to indicate which profile should be automatically selected.
+ * <p>Can only be used if there is a work profile.
+ * <p>Possible values can be either {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}.
+ */
+ static final String EXTRA_SELECTED_PROFILE =
+ "com.android.internal.app.ResolverActivity.EXTRA_SELECTED_PROFILE";
+
+ static final int PROFILE_PERSONAL = AbstractMultiProfilePagerAdapter.PROFILE_PERSONAL;
+ static final int PROFILE_WORK = AbstractMultiProfilePagerAdapter.PROFILE_WORK;
+
private BroadcastReceiver mWorkProfileStateReceiver;
private UserHandle mHeaderCreatorUser;
@@ -475,6 +486,10 @@
selectedProfile = PROFILE_WORK;
}
}
+ int selectedProfileExtra = getSelectedProfileExtra();
+ if (selectedProfileExtra != -1) {
+ selectedProfile = selectedProfileExtra;
+ }
// We only show the default app for the profile of the current user. The filterLastUsed
// flag determines whether to show a default app and that app is not shown in the
// resolver list. So filterLastUsed should be false for the other profile.
@@ -512,6 +527,25 @@
}
/**
+ * Returns {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK} if the {@link
+ * #EXTRA_SELECTED_PROFILE} extra was supplied, or {@code -1} if no extra was supplied.
+ * @throws IllegalArgumentException if the value passed to the {@link #EXTRA_SELECTED_PROFILE}
+ * extra is not {@link #PROFILE_PERSONAL} or {@link #PROFILE_WORK}
+ */
+ int getSelectedProfileExtra() {
+ int selectedProfile = -1;
+ if (getIntent().hasExtra(EXTRA_SELECTED_PROFILE)) {
+ selectedProfile = getIntent().getIntExtra(EXTRA_SELECTED_PROFILE, /* defValue = */ -1);
+ if (selectedProfile != PROFILE_PERSONAL && selectedProfile != PROFILE_WORK) {
+ throw new IllegalArgumentException(EXTRA_SELECTED_PROFILE + " has invalid value "
+ + selectedProfile + ". Must be either ResolverActivity.PROFILE_PERSONAL or "
+ + "ResolverActivity.PROFILE_WORK.");
+ }
+ }
+ return selectedProfile;
+ }
+
+ /**
* Returns the user id of the user that the starting intent originated from.
* <p>This is not necessarily equal to {@link #getUserId()} or {@link UserHandle#myUserId()},
* as there are edge cases when the intent resolver is launched in the other profile.
diff --git a/core/java/com/android/internal/net/VpnProfile.java b/core/java/com/android/internal/net/VpnProfile.java
index 829bd8a..8ea5aa8 100644
--- a/core/java/com/android/internal/net/VpnProfile.java
+++ b/core/java/com/android/internal/net/VpnProfile.java
@@ -136,13 +136,19 @@
public boolean isMetered = false; // 21
public int maxMtu = PlatformVpnProfile.MAX_MTU_DEFAULT; // 22
public boolean areAuthParamsInline = false; // 23
+ public final boolean isRestrictedToTestNetworks; // 24
// Helper fields.
@UnsupportedAppUsage
public transient boolean saveLogin = false;
public VpnProfile(String key) {
+ this(key, false);
+ }
+
+ public VpnProfile(String key, boolean isRestrictedToTestNetworks) {
this.key = key;
+ this.isRestrictedToTestNetworks = isRestrictedToTestNetworks;
}
@UnsupportedAppUsage
@@ -171,6 +177,7 @@
isMetered = in.readBoolean();
maxMtu = in.readInt();
areAuthParamsInline = in.readBoolean();
+ isRestrictedToTestNetworks = in.readBoolean();
}
/**
@@ -220,6 +227,7 @@
out.writeBoolean(isMetered);
out.writeInt(maxMtu);
out.writeBoolean(areAuthParamsInline);
+ out.writeBoolean(isRestrictedToTestNetworks);
}
/**
@@ -237,12 +245,21 @@
String[] values = new String(value, StandardCharsets.UTF_8).split(VALUE_DELIMITER, -1);
// Acceptable numbers of values are:
// 14-19: Standard profile, with option for serverCert, proxy
- // 24: Standard profile with serverCert, proxy and platform-VPN parameters.
- if ((values.length < 14 || values.length > 19) && values.length != 24) {
+ // 24: Standard profile with serverCert, proxy and platform-VPN parameters
+ // 25: Standard profile with platform-VPN parameters and isRestrictedToTestNetworks
+ if ((values.length < 14 || values.length > 19)
+ && values.length != 24 && values.length != 25) {
return null;
}
- VpnProfile profile = new VpnProfile(key);
+ final boolean isRestrictedToTestNetworks;
+ if (values.length >= 25) {
+ isRestrictedToTestNetworks = Boolean.parseBoolean(values[24]);
+ } else {
+ isRestrictedToTestNetworks = false;
+ }
+
+ VpnProfile profile = new VpnProfile(key, isRestrictedToTestNetworks);
profile.name = values[0];
profile.type = Integer.parseInt(values[1]);
if (profile.type < 0 || profile.type > TYPE_MAX) {
@@ -283,6 +300,8 @@
profile.areAuthParamsInline = Boolean.parseBoolean(values[23]);
}
+ // isRestrictedToTestNetworks (values[24]) assigned as part of the constructor
+
profile.saveLogin = !profile.username.isEmpty() || !profile.password.isEmpty();
return profile;
} catch (Exception e) {
@@ -330,6 +349,7 @@
builder.append(VALUE_DELIMITER).append(isMetered);
builder.append(VALUE_DELIMITER).append(maxMtu);
builder.append(VALUE_DELIMITER).append(areAuthParamsInline);
+ builder.append(VALUE_DELIMITER).append(isRestrictedToTestNetworks);
return builder.toString().getBytes(StandardCharsets.UTF_8);
}
@@ -421,7 +441,8 @@
return Objects.hash(
key, type, server, username, password, dnsServers, searchDomains, routes, mppe,
l2tpSecret, ipsecIdentifier, ipsecSecret, ipsecUserCert, ipsecCaCert, ipsecServerCert,
- proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline);
+ proxy, mAllowedAlgorithms, isBypassable, isMetered, maxMtu, areAuthParamsInline,
+ isRestrictedToTestNetworks);
}
/** Checks VPN profiles for interior equality. */
@@ -453,7 +474,8 @@
&& isBypassable == other.isBypassable
&& isMetered == other.isMetered
&& maxMtu == other.maxMtu
- && areAuthParamsInline == other.areAuthParamsInline;
+ && areAuthParamsInline == other.areAuthParamsInline
+ && isRestrictedToTestNetworks == other.isRestrictedToTestNetworks;
}
@NonNull
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 415e210..5a1af84 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -9875,6 +9875,10 @@
mPlatformIdleStateCallback = cb;
mRailEnergyDataCallback = railStatsCb;
mUserInfoProvider = userInfoProvider;
+
+ // Notify statsd that the system is initially not in doze.
+ mDeviceIdleMode = DEVICE_IDLE_MODE_OFF;
+ FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_IDLE_MODE_STATE_CHANGED, mDeviceIdleMode);
}
@UnsupportedAppUsage
diff --git a/data/sounds/AudioTv.mk b/data/sounds/AudioTv.mk
index 2a31e4c..fd53aff 100644
--- a/data/sounds/AudioTv.mk
+++ b/data/sounds/AudioTv.mk
@@ -16,6 +16,7 @@
PRODUCT_COPY_FILES += \
$(LOCAL_PATH)/Alarm_Beep_01.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/alarms/Alarm_Beep_02.ogg \
+ $(LOCAL_PATH)/effects/ogg/Effect_Tick_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/Effect_Tick.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressDelete_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressDelete.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressInvalid_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressInvalid.ogg \
$(LOCAL_PATH)/effects/ogg/KeypressReturn_120_48k.ogg:$(TARGET_COPY_OUT_PRODUCT)/media/audio/ui/KeypressReturn.ogg \
diff --git a/graphics/java/android/graphics/pdf/PdfDocument.java b/graphics/java/android/graphics/pdf/PdfDocument.java
index 1b8336f..58421ab 100644
--- a/graphics/java/android/graphics/pdf/PdfDocument.java
+++ b/graphics/java/android/graphics/pdf/PdfDocument.java
@@ -46,8 +46,8 @@
* // create a new document
* PdfDocument document = new PdfDocument();
*
- * // crate a page description
- * PageInfo pageInfo = new PageInfo.Builder(new Rect(0, 0, 100, 100), 1).create();
+ * // create a page description
+ * PageInfo pageInfo = new PageInfo.Builder(100, 100, 1).create();
*
* // start a page
* Page page = document.startPage(pageInfo);
diff --git a/media/jni/soundpool/Android.bp b/media/jni/soundpool/Android.bp
index aa32793..6141308 100644
--- a/media/jni/soundpool/Android.bp
+++ b/media/jni/soundpool/Android.bp
@@ -1,5 +1,92 @@
+tidy_errors = [
+ // https://clang.llvm.org/extra/clang-tidy/checks/list.html
+ // For many categories, the checks are too many to specify individually.
+ // Feel free to disable as needed - as warnings are generally ignored,
+ // we treat warnings as errors.
+ "android-*",
+ "bugprone-*",
+ "cert-*",
+ "clang-analyzer-security*",
+ "google-*",
+ "misc-*",
+ //"modernize-*", // explicitly list the modernize as they can be subjective.
+ "modernize-avoid-bind",
+ //"modernize-avoid-c-arrays", // std::array<> can be verbose
+ "modernize-concat-nested-namespaces",
+ //"modernize-deprecated-headers", // C headers still ok even if there is C++ equivalent.
+ "modernize-deprecated-ios-base-aliases",
+ "modernize-loop-convert",
+ "modernize-make-shared",
+ "modernize-make-unique",
+ "modernize-pass-by-value",
+ "modernize-raw-string-literal",
+ "modernize-redundant-void-arg",
+ "modernize-replace-auto-ptr",
+ "modernize-replace-random-shuffle",
+ "modernize-return-braced-init-list",
+ "modernize-shrink-to-fit",
+ "modernize-unary-static-assert",
+ "modernize-use-auto", // debatable - auto can obscure type
+ "modernize-use-bool-literals",
+ "modernize-use-default-member-init",
+ "modernize-use-emplace",
+ "modernize-use-equals-default",
+ "modernize-use-equals-delete",
+ "modernize-use-nodiscard",
+ "modernize-use-noexcept",
+ "modernize-use-nullptr",
+ "modernize-use-override",
+ //"modernize-use-trailing-return-type", // not necessarily more readable
+ "modernize-use-transparent-functors",
+ "modernize-use-uncaught-exceptions",
+ "modernize-use-using",
+ "performance-*",
+
+ // Remove some pedantic stylistic requirements.
+ "-google-readability-casting", // C++ casts not always necessary and may be verbose
+ "-google-readability-todo", // do not require TODO(info)
+ "-google-build-using-namespace", // Reenable and fix later.
+]
+
+cc_defaults {
+ name: "soundpool_flags_defaults",
+ // https://clang.llvm.org/docs/UsersManual.html#command-line-options
+ // https://clang.llvm.org/docs/DiagnosticsReference.html
+ cflags: [
+ "-Wall",
+ "-Wdeprecated",
+ "-Werror",
+ "-Werror=implicit-fallthrough",
+ "-Werror=sometimes-uninitialized",
+ //"-Werror=conditional-uninitialized",
+ "-Wextra",
+ "-Wredundant-decls",
+ "-Wshadow",
+ "-Wstrict-aliasing",
+ "-fstrict-aliasing",
+ "-Wthread-safety",
+ //"-Wthread-safety-negative", // experimental - looks broken in R.
+ "-Wunreachable-code",
+ "-Wunreachable-code-break",
+ "-Wunreachable-code-return",
+ "-Wunused",
+ "-Wused-but-marked-unused",
+ ],
+ // https://clang.llvm.org/extra/clang-tidy/
+ tidy: true,
+ tidy_checks: tidy_errors,
+ tidy_checks_as_errors: tidy_errors,
+ tidy_flags: [
+ "-format-style='file'",
+ "--header-filter='frameworks/base/media/jni/soundpool'",
+ ],
+}
+
cc_library_shared {
name: "libsoundpool",
+ defaults: [
+ "soundpool_flags_defaults",
+ ],
srcs: [
"android_media_SoundPool.cpp",
diff --git a/media/jni/soundpool/Sound.cpp b/media/jni/soundpool/Sound.cpp
index 0bbc3e4..c3abdc2 100644
--- a/media/jni/soundpool/Sound.cpp
+++ b/media/jni/soundpool/Sound.cpp
@@ -31,7 +31,7 @@
Sound::Sound(int32_t soundID, int fd, int64_t offset, int64_t length)
: mSoundID(soundID)
- , mFd(dup(fd))
+ , mFd(fcntl(fd, F_DUPFD_CLOEXEC)) // like dup(fd) but closes on exec to prevent leaks.
, mOffset(offset)
, mLength(length)
{
@@ -47,7 +47,7 @@
static status_t decode(int fd, int64_t offset, int64_t length,
uint32_t *rate, int32_t *channelCount, audio_format_t *audioFormat,
- audio_channel_mask_t *channelMask, sp<MemoryHeapBase> heap,
+ audio_channel_mask_t *channelMask, const sp<MemoryHeapBase>& heap,
size_t *sizeInBytes) {
ALOGV("%s(fd=%d, offset=%lld, length=%lld, ...)",
__func__, fd, (long long)offset, (long long)length);
@@ -81,7 +81,7 @@
bool sawInputEOS = false;
bool sawOutputEOS = false;
- uint8_t* writePos = static_cast<uint8_t*>(heap->getBase());
+ auto writePos = static_cast<uint8_t*>(heap->getBase());
size_t available = heap->getSize();
size_t written = 0;
format.reset(AMediaCodec_getOutputFormat(codec.get())); // update format.
@@ -204,7 +204,7 @@
int32_t channelCount;
audio_format_t format;
audio_channel_mask_t channelMask;
- status_t status = decode(mFd.get(), mOffset, mLength, &sampleRate, &channelCount, &format,
+ status = decode(mFd.get(), mOffset, mLength, &sampleRate, &channelCount, &format,
&channelMask, mHeap, &mSizeInBytes);
ALOGV("%s: close(%d)", __func__, mFd.get());
mFd.reset(); // close
diff --git a/media/jni/soundpool/SoundDecoder.cpp b/media/jni/soundpool/SoundDecoder.cpp
index 12200ef..6614fdb 100644
--- a/media/jni/soundpool/SoundDecoder.cpp
+++ b/media/jni/soundpool/SoundDecoder.cpp
@@ -57,7 +57,7 @@
mThreadPool->quit();
}
-void SoundDecoder::run(int32_t id __unused /* ALOGV only */)
+void SoundDecoder::run(int32_t id)
{
ALOGV("%s(%d): entering", __func__, id);
std::unique_lock lock(mLock);
diff --git a/media/jni/soundpool/SoundDecoder.h b/media/jni/soundpool/SoundDecoder.h
index 1288943..7b62114 100644
--- a/media/jni/soundpool/SoundDecoder.h
+++ b/media/jni/soundpool/SoundDecoder.h
@@ -30,21 +30,22 @@
public:
SoundDecoder(SoundManager* soundManager, size_t threads);
~SoundDecoder();
- void loadSound(int32_t soundID);
+ void loadSound(int32_t soundID) NO_THREAD_SAFETY_ANALYSIS; // uses unique_lock
void quit();
private:
- void run(int32_t id); // The decode thread function.
+ // The decode thread function.
+ void run(int32_t id) NO_THREAD_SAFETY_ANALYSIS; // uses unique_lock
SoundManager* const mSoundManager; // set in constructor, has own lock
std::unique_ptr<ThreadPool> mThreadPool; // set in constructor, has own lock
std::mutex mLock;
- std::condition_variable mQueueSpaceAvailable;
- std::condition_variable mQueueDataAvailable;
+ std::condition_variable mQueueSpaceAvailable GUARDED_BY(mLock);
+ std::condition_variable mQueueDataAvailable GUARDED_BY(mLock);
- std::deque<int32_t> mSoundIDs; // GUARDED_BY(mLock);
- bool mQuit = false; // GUARDED_BY(mLock);
+ std::deque<int32_t> mSoundIDs GUARDED_BY(mLock);
+ bool mQuit GUARDED_BY(mLock) = false;
};
} // end namespace android::soundpool
diff --git a/media/jni/soundpool/SoundManager.cpp b/media/jni/soundpool/SoundManager.cpp
index 3c625bf..5b16174 100644
--- a/media/jni/soundpool/SoundManager.cpp
+++ b/media/jni/soundpool/SoundManager.cpp
@@ -43,7 +43,7 @@
mSounds.clear();
}
-int32_t SoundManager::load(int fd, int64_t offset, int64_t length, int32_t priority __unused)
+int32_t SoundManager::load(int fd, int64_t offset, int64_t length, int32_t priority)
{
ALOGV("%s(fd=%d, offset=%lld, length=%lld, priority=%d)",
__func__, fd, (long long)offset, (long long)length, priority);
diff --git a/media/jni/soundpool/SoundManager.h b/media/jni/soundpool/SoundManager.h
index 9201e78..4a4e3b8 100644
--- a/media/jni/soundpool/SoundManager.h
+++ b/media/jni/soundpool/SoundManager.h
@@ -21,6 +21,8 @@
#include <mutex>
#include <unordered_map>
+#include <android-base/thread_annotations.h>
+
namespace android {
class SoundPool;
@@ -91,20 +93,21 @@
}
private:
mutable std::recursive_mutex mCallbackLock; // allow mCallback to setCallback().
+ // No thread-safety checks in R for recursive_mutex.
SoundPool* mSoundPool = nullptr; // GUARDED_BY(mCallbackLock)
SoundPoolCallback* mCallback = nullptr; // GUARDED_BY(mCallbackLock)
void* mUserData = nullptr; // GUARDED_BY(mCallbackLock)
};
- std::shared_ptr<Sound> findSound_l(int32_t soundID) const;
+ std::shared_ptr<Sound> findSound_l(int32_t soundID) const REQUIRES(mSoundManagerLock);
// The following variables are initialized in constructor and can be accessed anytime.
- CallbackHandler mCallbackHandler; // has its own lock
- const std::unique_ptr<SoundDecoder> mDecoder; // has its own lock
+ CallbackHandler mCallbackHandler; // has its own lock
+ const std::unique_ptr<SoundDecoder> mDecoder; // has its own lock
- mutable std::mutex mSoundManagerLock;
- std::unordered_map<int, std::shared_ptr<Sound>> mSounds; // GUARDED_BY(mSoundManagerLock)
- int32_t mNextSoundID = 0; // GUARDED_BY(mSoundManagerLock)
+ mutable std::mutex mSoundManagerLock;
+ std::unordered_map<int, std::shared_ptr<Sound>> mSounds GUARDED_BY(mSoundManagerLock);
+ int32_t mNextSoundID GUARDED_BY(mSoundManagerLock) = 0;
};
} // namespace android::soundpool
diff --git a/media/jni/soundpool/Stream.cpp b/media/jni/soundpool/Stream.cpp
index e3152d6..a6d9758 100644
--- a/media/jni/soundpool/Stream.cpp
+++ b/media/jni/soundpool/Stream.cpp
@@ -105,7 +105,7 @@
if (streamID == mStreamID) {
mRate = rate;
if (mAudioTrack != nullptr && mSound != nullptr) {
- const uint32_t sampleRate = uint32_t(float(mSound->getSampleRate()) * rate + 0.5);
+ const auto sampleRate = (uint32_t)lround(double(mSound->getSampleRate()) * rate);
mAudioTrack->setSampleRate(sampleRate);
}
}
@@ -214,8 +214,11 @@
void Stream::clearAudioTrack()
{
+ sp<AudioTrack> release; // release outside of lock.
+ std::lock_guard lock(mLock);
// This will invoke the destructor which waits for the AudioTrack thread to join,
// and is currently the only safe way to ensure there are no callbacks afterwards.
+ release = mAudioTrack; // or std::swap if we had move semantics.
mAudioTrack.clear();
}
@@ -288,7 +291,7 @@
const audio_stream_type_t streamType =
AudioSystem::attributesToStreamType(*mStreamManager->getAttributes());
const int32_t channelCount = sound->getChannelCount();
- const uint32_t sampleRate = uint32_t(float(sound->getSampleRate()) * rate + 0.5);
+ const auto sampleRate = (uint32_t)lround(double(sound->getSampleRate()) * rate);
size_t frameCount = 0;
if (loop) {
@@ -307,7 +310,7 @@
__func__, mAudioTrack.get(), sound->getSoundID());
}
}
- if (newTrack == 0) {
+ if (newTrack == nullptr) {
// mToggle toggles each time a track is started on a given stream.
// The toggle is concatenated with the Stream address and passed to AudioTrack
// as callback user data. This enables the detection of callbacks received from the old
@@ -380,9 +383,9 @@
/* static */
void Stream::staticCallback(int event, void* user, void* info)
{
- const uintptr_t userAsInt = (uintptr_t)user;
- Stream* stream = reinterpret_cast<Stream*>(userAsInt & ~1);
- stream->callback(event, info, userAsInt & 1, 0 /* tries */);
+ const auto userAsInt = (uintptr_t)user;
+ auto stream = reinterpret_cast<Stream*>(userAsInt & ~1);
+ stream->callback(event, info, int(userAsInt & 1), 0 /* tries */);
}
void Stream::callback(int event, void* info, int toggle, int tries)
diff --git a/media/jni/soundpool/Stream.h b/media/jni/soundpool/Stream.h
index 82d2690..fd92921 100644
--- a/media/jni/soundpool/Stream.h
+++ b/media/jni/soundpool/Stream.h
@@ -18,6 +18,7 @@
#include "Sound.h"
+#include <android-base/thread_annotations.h>
#include <audio_utils/clock.h>
#include <media/AudioTrack.h>
@@ -104,47 +105,55 @@
// The following getters are not locked and have weak consistency.
// These are considered advisory only - being stale is of nuisance.
- int32_t getPriority() const { return mPriority; }
- int32_t getPairPriority() const { return getPairStream()->getPriority(); }
- int64_t getStopTimeNs() const { return mStopTimeNs; }
+ int32_t getPriority() const NO_THREAD_SAFETY_ANALYSIS { return mPriority; }
+ int32_t getPairPriority() const NO_THREAD_SAFETY_ANALYSIS {
+ return getPairStream()->getPriority();
+ }
+ int64_t getStopTimeNs() const NO_THREAD_SAFETY_ANALYSIS { return mStopTimeNs; }
- int32_t getStreamID() const { return mStreamID; } // Can change with setPlay()
- int32_t getSoundID() const { return mSoundID; } // Can change with play_l()
- bool hasSound() const { return mSound.get() != nullptr; }
+ // Can change with setPlay()
+ int32_t getStreamID() const NO_THREAD_SAFETY_ANALYSIS { return mStreamID; }
- Stream* getPairStream() const; // this never changes. See top of header.
+ // Can change with play_l()
+ int32_t getSoundID() const NO_THREAD_SAFETY_ANALYSIS { return mSoundID; }
+
+ bool hasSound() const NO_THREAD_SAFETY_ANALYSIS { return mSound.get() != nullptr; }
+
+ // This never changes. See top of header.
+ Stream* getPairStream() const;
private:
void play_l(const std::shared_ptr<Sound>& sound, int streamID,
float leftVolume, float rightVolume, int priority, int loop, float rate,
- sp<AudioTrack> releaseTracks[2]);
- void stop_l();
- void setVolume_l(float leftVolume, float rightVolume);
+ sp<AudioTrack> releaseTracks[2]) REQUIRES(mLock);
+ void stop_l() REQUIRES(mLock);
+ void setVolume_l(float leftVolume, float rightVolume) REQUIRES(mLock);
// For use with AudioTrack callback.
static void staticCallback(int event, void* user, void* info);
- void callback(int event, void* info, int toggle, int tries);
+ void callback(int event, void* info, int toggle, int tries)
+ NO_THREAD_SAFETY_ANALYSIS; // uses unique_lock
// StreamManager should be set on construction and not changed.
// release mLock before calling into StreamManager
StreamManager* mStreamManager = nullptr;
mutable std::mutex mLock;
- std::atomic_int32_t mStreamID = 0; // Note: valid streamIDs are always positive.
- int mState = IDLE;
- std::shared_ptr<Sound> mSound; // Non-null if playing.
- int32_t mSoundID = 0; // The sound ID associated with the AudioTrack.
- float mLeftVolume = 0.f;
- float mRightVolume = 0.f;
- int32_t mPriority = INT32_MIN;
- int32_t mLoop = 0;
- float mRate = 0.f;
- bool mAutoPaused = false;
- bool mMuted = false;
+ std::atomic_int32_t mStreamID GUARDED_BY(mLock) = 0; // Valid streamIDs are always positive.
+ int mState GUARDED_BY(mLock) = IDLE;
+ std::shared_ptr<Sound> mSound GUARDED_BY(mLock); // Non-null if playing.
+ int32_t mSoundID GUARDED_BY(mLock) = 0; // SoundID associated with AudioTrack.
+ float mLeftVolume GUARDED_BY(mLock) = 0.f;
+ float mRightVolume GUARDED_BY(mLock) = 0.f;
+ int32_t mPriority GUARDED_BY(mLock) = INT32_MIN;
+ int32_t mLoop GUARDED_BY(mLock) = 0;
+ float mRate GUARDED_BY(mLock) = 0.f;
+ bool mAutoPaused GUARDED_BY(mLock) = false;
+ bool mMuted GUARDED_BY(mLock) = false;
- sp<AudioTrack> mAudioTrack;
- int mToggle = 0;
- int64_t mStopTimeNs = 0; // if nonzero, time to wait for stop.
+ sp<AudioTrack> mAudioTrack GUARDED_BY(mLock);
+ int mToggle GUARDED_BY(mLock) = 0;
+ int64_t mStopTimeNs GUARDED_BY(mLock) = 0; // if nonzero, time to wait for stop.
};
} // namespace android::soundpool
diff --git a/media/jni/soundpool/StreamManager.cpp b/media/jni/soundpool/StreamManager.cpp
index c612218..9ff4254 100644
--- a/media/jni/soundpool/StreamManager.cpp
+++ b/media/jni/soundpool/StreamManager.cpp
@@ -55,7 +55,7 @@
streams = 1;
}
mStreamPoolSize = streams * 2;
- mStreamPool.reset(new Stream[mStreamPoolSize]);
+ mStreamPool = std::make_unique<Stream[]>(mStreamPoolSize); // create array of streams.
// we use a perfect hash table with 2x size to map StreamIDs to Stream pointers.
mPerfectHash = std::make_unique<PerfectHash<int32_t, Stream *>>(roundup(mStreamPoolSize * 2));
}
@@ -69,7 +69,7 @@
size_t StreamMap::streamPosition(const Stream* stream) const
{
ptrdiff_t index = stream - mStreamPool.get();
- LOG_ALWAYS_FATAL_IF(index < 0 || index >= mStreamPoolSize,
+ LOG_ALWAYS_FATAL_IF(index < 0 || (size_t)index >= mStreamPoolSize,
"%s: stream position out of range: %td", __func__, index);
return (size_t)index;
}
@@ -92,6 +92,11 @@
////////////
+// Thread safety analysis is supposed to be disabled for constructors and destructors
+// but clang in R seems to have a bug. We use pragma to disable.
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wthread-safety-analysis"
+
StreamManager::StreamManager(
int32_t streams, size_t threads, const audio_attributes_t* attributes)
: StreamMap(streams)
@@ -110,6 +115,8 @@
"SoundPool_");
}
+#pragma clang diagnostic pop
+
StreamManager::~StreamManager()
{
ALOGV("%s", __func__);
diff --git a/media/jni/soundpool/StreamManager.h b/media/jni/soundpool/StreamManager.h
index 30ad220..59ae2f9 100644
--- a/media/jni/soundpool/StreamManager.h
+++ b/media/jni/soundpool/StreamManager.h
@@ -183,9 +183,9 @@
std::atomic_size_t mActiveThreadCount = 0;
std::mutex mThreadLock;
- bool mQuit = false; // GUARDED_BY(mThreadLock)
- int32_t mNextThreadId = 0; // GUARDED_BY(mThreadLock)
- std::list<std::unique_ptr<JavaThread>> mThreads; // GUARDED_BY(mThreadLock)
+ bool mQuit GUARDED_BY(mThreadLock) = false;
+ int32_t mNextThreadId GUARDED_BY(mThreadLock) = 0;
+ std::list<std::unique_ptr<JavaThread>> mThreads GUARDED_BY(mThreadLock);
};
/**
@@ -263,7 +263,7 @@
mutable std::mutex mHashLock;
const size_t mHashCapacity; // size of mK2V no lock needed.
std::unique_ptr<std::atomic<V>[]> mK2V; // no lock needed for read access.
- K mNextKey{}; // GUARDED_BY(mHashLock)
+ K mNextKey GUARDED_BY(mHashLock) {};
};
/**
@@ -392,7 +392,8 @@
// Returns positive streamID on success, 0 on failure. This is locked.
int32_t queueForPlay(const std::shared_ptr<Sound> &sound,
int32_t soundID, float leftVolume, float rightVolume,
- int32_t priority, int32_t loop, float rate);
+ int32_t priority, int32_t loop, float rate)
+ NO_THREAD_SAFETY_ANALYSIS; // uses unique_lock
///////////////////////////////////////////////////////////////////////
// Called from soundpool::Stream
@@ -407,11 +408,11 @@
private:
- void run(int32_t id); // worker thread, takes lock internally.
+ void run(int32_t id) NO_THREAD_SAFETY_ANALYSIS; // worker thread, takes unique_lock.
void dump() const; // no lock needed
// returns true if more worker threads are needed.
- bool needMoreThreads_l() {
+ bool needMoreThreads_l() REQUIRES(mStreamManagerLock) {
return mRestartStreams.size() > 0 &&
(mThreadPool->getActiveThreadCount() == 0
|| std::distance(mRestartStreams.begin(),
@@ -420,14 +421,16 @@
}
// returns true if the stream was added.
- bool moveToRestartQueue_l(Stream* stream, int32_t activeStreamIDToMatch = 0);
+ bool moveToRestartQueue_l(
+ Stream* stream, int32_t activeStreamIDToMatch = 0) REQUIRES(mStreamManagerLock);
// returns number of queues the stream was removed from (should be 0 or 1);
// a special code of -1 is returned if activeStreamIDToMatch is > 0 and
// the stream wasn't found on the active queue.
- ssize_t removeFromQueues_l(Stream* stream, int32_t activeStreamIDToMatch = 0);
- void addToRestartQueue_l(Stream *stream);
- void addToActiveQueue_l(Stream *stream);
- void sanityCheckQueue_l() const;
+ ssize_t removeFromQueues_l(
+ Stream* stream, int32_t activeStreamIDToMatch = 0) REQUIRES(mStreamManagerLock);
+ void addToRestartQueue_l(Stream *stream) REQUIRES(mStreamManagerLock);
+ void addToActiveQueue_l(Stream *stream) REQUIRES(mStreamManagerLock);
+ void sanityCheckQueue_l() const REQUIRES(mStreamManagerLock);
const audio_attributes_t mAttributes;
std::unique_ptr<ThreadPool> mThreadPool; // locked internally
@@ -436,9 +439,9 @@
// 4 stream queues by the Manager Thread or by the user initiated play().
// A stream pair has exactly one stream on exactly one of the queues.
std::mutex mStreamManagerLock;
- std::condition_variable mStreamManagerCondition;
+ std::condition_variable mStreamManagerCondition GUARDED_BY(mStreamManagerLock);
- bool mQuit = false; // GUARDED_BY(mStreamManagerLock)
+ bool mQuit GUARDED_BY(mStreamManagerLock) = false;
// There are constructor arg "streams" pairs of streams, only one of each
// pair on the 4 stream queues below. The other stream in the pair serves as
@@ -452,24 +455,24 @@
// The paired stream may be active (but with no AudioTrack), and will be restarted
// with an active AudioTrack when the current stream is stopped.
std::multimap<int64_t /* stopTimeNs */, Stream*>
- mRestartStreams; // GUARDED_BY(mStreamManagerLock)
+ mRestartStreams GUARDED_BY(mStreamManagerLock);
// 2) mActiveStreams: Streams that are active.
// The paired stream will be inactive.
// This is in order of specified by kStealActiveStream_OldestFirst
- std::list<Stream*> mActiveStreams; // GUARDED_BY(mStreamManagerLock)
+ std::list<Stream*> mActiveStreams GUARDED_BY(mStreamManagerLock);
// 3) mAvailableStreams: Streams that are inactive.
// The paired stream will also be inactive.
// No particular order.
- std::unordered_set<Stream*> mAvailableStreams; // GUARDED_BY(mStreamManagerLock)
+ std::unordered_set<Stream*> mAvailableStreams GUARDED_BY(mStreamManagerLock);
// 4) mProcessingStreams: Streams that are being processed by the ManagerThreads
// When on this queue, the stream and its pair are not available for stealing.
// Each ManagerThread will have at most one stream on the mProcessingStreams queue.
// The paired stream may be active or restarting.
// No particular order.
- std::unordered_set<Stream*> mProcessingStreams; // GUARDED_BY(mStreamManagerLock)
+ std::unordered_set<Stream*> mProcessingStreams GUARDED_BY(mStreamManagerLock);
};
} // namespace android::soundpool
diff --git a/media/jni/soundpool/android_media_SoundPool.cpp b/media/jni/soundpool/android_media_SoundPool.cpp
index f670636..8f6df3d 100644
--- a/media/jni/soundpool/android_media_SoundPool.cpp
+++ b/media/jni/soundpool/android_media_SoundPool.cpp
@@ -52,7 +52,7 @@
{
ALOGV("android_media_SoundPool_load_FD");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return 0;
+ if (ap == nullptr) return 0;
return (jint) ap->load(jniGetFDFromFileDescriptor(env, fileDescriptor),
int64_t(offset), int64_t(length), int(priority));
}
@@ -61,7 +61,7 @@
android_media_SoundPool_unload(JNIEnv *env, jobject thiz, jint sampleID) {
ALOGV("android_media_SoundPool_unload\n");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return JNI_FALSE;
+ if (ap == nullptr) return JNI_FALSE;
return ap->unload(sampleID) ? JNI_TRUE : JNI_FALSE;
}
@@ -72,7 +72,7 @@
{
ALOGV("android_media_SoundPool_play\n");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return 0;
+ if (ap == nullptr) return 0;
return (jint) ap->play(sampleID, leftVolume, rightVolume, priority, loop, rate);
}
@@ -81,7 +81,7 @@
{
ALOGV("android_media_SoundPool_pause");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return;
+ if (ap == nullptr) return;
ap->pause(channelID);
}
@@ -90,7 +90,7 @@
{
ALOGV("android_media_SoundPool_resume");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return;
+ if (ap == nullptr) return;
ap->resume(channelID);
}
@@ -99,7 +99,7 @@
{
ALOGV("android_media_SoundPool_autoPause");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return;
+ if (ap == nullptr) return;
ap->autoPause();
}
@@ -108,7 +108,7 @@
{
ALOGV("android_media_SoundPool_autoResume");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return;
+ if (ap == nullptr) return;
ap->autoResume();
}
@@ -117,7 +117,7 @@
{
ALOGV("android_media_SoundPool_stop");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return;
+ if (ap == nullptr) return;
ap->stop(channelID);
}
@@ -127,7 +127,7 @@
{
ALOGV("android_media_SoundPool_setVolume");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return;
+ if (ap == nullptr) return;
ap->setVolume(channelID, (float) leftVolume, (float) rightVolume);
}
@@ -136,7 +136,7 @@
{
ALOGV("android_media_SoundPool_mute(%d)", muting);
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return;
+ if (ap == nullptr) return;
ap->mute(muting == JNI_TRUE);
}
@@ -146,7 +146,7 @@
{
ALOGV("android_media_SoundPool_setPriority");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return;
+ if (ap == nullptr) return;
ap->setPriority(channelID, (int) priority);
}
@@ -156,7 +156,7 @@
{
ALOGV("android_media_SoundPool_setLoop");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return;
+ if (ap == nullptr) return;
ap->setLoop(channelID, loop);
}
@@ -166,7 +166,7 @@
{
ALOGV("android_media_SoundPool_setRate");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap == NULL) return;
+ if (ap == nullptr) return;
ap->setRate(channelID, (float) rate);
}
@@ -174,24 +174,26 @@
{
ALOGV("callback: (%d, %d, %d, %p, %p)", event.mMsg, event.mArg1, event.mArg2, soundPool, user);
JNIEnv *env = AndroidRuntime::getJNIEnv();
- env->CallStaticVoidMethod(fields.mSoundPoolClass, fields.mPostEvent, user, event.mMsg, event.mArg1, event.mArg2, NULL);
+ env->CallStaticVoidMethod(
+ fields.mSoundPoolClass, fields.mPostEvent, user, event.mMsg, event.mArg1, event.mArg2,
+ nullptr /* object */);
}
static jint
android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef,
jint maxChannels, jobject jaa)
{
- if (jaa == 0) {
+ if (jaa == nullptr) {
ALOGE("Error creating SoundPool: invalid audio attributes");
return -1;
}
- audio_attributes_t *paa = NULL;
+ audio_attributes_t *paa = nullptr;
// read the AudioAttributes values
paa = (audio_attributes_t *) calloc(1, sizeof(audio_attributes_t));
- const jstring jtags =
+ const auto jtags =
(jstring) env->GetObjectField(jaa, javaAudioAttrFields.fieldFormattedTags);
- const char* tags = env->GetStringUTFChars(jtags, NULL);
+ const char* tags = env->GetStringUTFChars(jtags, nullptr);
// copying array size -1, char array for tags was calloc'd, no need to NULL-terminate it
strncpy(paa->tags, tags, AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - 1);
env->ReleaseStringUTFChars(jtags, tags);
@@ -201,8 +203,8 @@
paa->flags = env->GetIntField(jaa, javaAudioAttrFields.fieldFlags);
ALOGV("android_media_SoundPool_native_setup");
- SoundPool *ap = new SoundPool(maxChannels, paa);
- if (ap == NULL) {
+ auto *ap = new SoundPool(maxChannels, paa);
+ if (ap == nullptr) {
return -1;
}
@@ -224,12 +226,12 @@
{
ALOGV("android_media_SoundPool_release");
SoundPool *ap = MusterSoundPool(env, thiz);
- if (ap != NULL) {
+ if (ap != nullptr) {
// release weak reference and clear callback
- jobject weakRef = (jobject) ap->getUserData();
- ap->setCallback(NULL, NULL);
- if (weakRef != NULL) {
+ auto weakRef = (jobject) ap->getUserData();
+ ap->setCallback(nullptr /* callback */, nullptr /* user */);
+ if (weakRef != nullptr) {
env->DeleteGlobalRef(weakRef);
}
@@ -309,7 +311,7 @@
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
- JNIEnv* env = NULL;
+ JNIEnv* env = nullptr;
jint result = -1;
jclass clazz;
@@ -317,23 +319,23 @@
ALOGE("ERROR: GetEnv failed\n");
return result;
}
- assert(env != NULL);
+ assert(env != nullptr);
clazz = env->FindClass(kClassPathName);
- if (clazz == NULL) {
+ if (clazz == nullptr) {
ALOGE("Can't find %s", kClassPathName);
return result;
}
fields.mNativeContext = env->GetFieldID(clazz, "mNativeContext", "J");
- if (fields.mNativeContext == NULL) {
+ if (fields.mNativeContext == nullptr) {
ALOGE("Can't find SoundPool.mNativeContext");
return result;
}
fields.mPostEvent = env->GetStaticMethodID(clazz, "postEventFromNative",
"(Ljava/lang/Object;IIILjava/lang/Object;)V");
- if (fields.mPostEvent == NULL) {
+ if (fields.mPostEvent == nullptr) {
ALOGE("Can't find android/media/SoundPool.postEventFromNative");
return result;
}
@@ -342,16 +344,18 @@
// since it's a static object.
fields.mSoundPoolClass = (jclass) env->NewGlobalRef(clazz);
- if (AndroidRuntime::registerNativeMethods(env, kClassPathName, gMethods, NELEM(gMethods)) < 0)
+ if (AndroidRuntime::registerNativeMethods(
+ env, kClassPathName, gMethods, NELEM(gMethods)) < 0) {
return result;
+ }
// Get the AudioAttributes class and fields
jclass audioAttrClass = env->FindClass(kAudioAttributesClassPathName);
- if (audioAttrClass == NULL) {
+ if (audioAttrClass == nullptr) {
ALOGE("Can't find %s", kAudioAttributesClassPathName);
return result;
}
- jclass audioAttributesClassRef = (jclass)env->NewGlobalRef(audioAttrClass);
+ auto audioAttributesClassRef = (jclass)env->NewGlobalRef(audioAttrClass);
javaAudioAttrFields.fieldUsage = env->GetFieldID(audioAttributesClassRef, "mUsage", "I");
javaAudioAttrFields.fieldContentType
= env->GetFieldID(audioAttributesClassRef, "mContentType", "I");
@@ -359,9 +363,10 @@
javaAudioAttrFields.fieldFormattedTags =
env->GetFieldID(audioAttributesClassRef, "mFormattedTags", "Ljava/lang/String;");
env->DeleteGlobalRef(audioAttributesClassRef);
- if (javaAudioAttrFields.fieldUsage == NULL || javaAudioAttrFields.fieldContentType == NULL
- || javaAudioAttrFields.fieldFlags == NULL
- || javaAudioAttrFields.fieldFormattedTags == NULL) {
+ if (javaAudioAttrFields.fieldUsage == nullptr
+ || javaAudioAttrFields.fieldContentType == nullptr
+ || javaAudioAttrFields.fieldFlags == nullptr
+ || javaAudioAttrFields.fieldFormattedTags == nullptr) {
ALOGE("Can't initialize AudioAttributes fields");
return result;
}
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a13451f..7f1763d 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -907,6 +907,8 @@
<dimen name="screen_pinning_nav_highlight_size">56dp</dimen>
<!-- Screen pinning inner nav bar outer circle size -->
<dimen name="screen_pinning_nav_highlight_outer_size">84dp</dimen>
+ <!-- Screen pinning description bullet gap width -->
+ <dimen name="screen_pinning_description_bullet_gap_width">6sp</dimen>
<!-- Padding to be used on the bottom of the fingerprint icon on Keyguard so it better aligns
with the other icons. -->
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f7f1ef6..21a2435 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1522,7 +1522,7 @@
<string name="accessibility_output_chooser">Switch output device</string>
<!-- Screen pinning dialog title. -->
- <string name="screen_pinning_title">Screen is pinned</string>
+ <string name="screen_pinning_title">App is pinned</string>
<!-- Screen pinning dialog description. -->
<string name="screen_pinning_description">This keeps it in view until you unpin. Touch & hold Back and Overview to unpin.</string>
<string name="screen_pinning_description_recents_invisible">This keeps it in view until you unpin. Touch & hold Back and Home to unpin.</string>
@@ -1530,20 +1530,24 @@
<!-- Screen pinning dialog description. -->
<string name="screen_pinning_description_accessible">This keeps it in view until you unpin. Touch & hold Overview to unpin.</string>
<string name="screen_pinning_description_recents_invisible_accessible">This keeps it in view until you unpin. Touch & hold Home to unpin.</string>
+ <!-- Screen pinning security warning: personal data, email, contacts may be exposed while screen is pinned. [CHAR LIMIT=NONE] -->
+ <string name="screen_pinning_exposes_personal_data">Personal data may be accessible (such as contacts and email content).</string>
+ <!-- Screen pinning security warning: a pinned app can still launch other apps. [CHAR LIMIT=NONE] -->
+ <string name="screen_pinning_can_open_other_apps">Pinned app may open other apps.</string>
<!-- Notify use that they are in Lock-to-app -->
- <string name="screen_pinning_toast">To unpin this screen, touch & hold Back and Overview
+ <string name="screen_pinning_toast">To unpin this app, touch & hold Back and Overview
buttons</string>
- <string name="screen_pinning_toast_recents_invisible">To unpin this screen, touch & hold Back
+ <string name="screen_pinning_toast_recents_invisible">To unpin this app, touch & hold Back
and Home buttons</string>
<!-- Notify (in toast) user how to unpin screen in gesture navigation mode [CHAR LIMIT=NONE] -->
- <string name="screen_pinning_toast_gesture_nav">To unpin this screen, swipe up & hold</string>
+ <string name="screen_pinning_toast_gesture_nav">To unpin this app, swipe up & hold</string>
<!-- Screen pinning positive response. -->
<string name="screen_pinning_positive">Got it</string>
<!-- Screen pinning negative response. -->
<string name="screen_pinning_negative">No thanks</string>
<!-- Enter/Exiting screen pinning indication. -->
- <string name="screen_pinning_start">Screen pinned</string>
- <string name="screen_pinning_exit">Screen unpinned</string>
+ <string name="screen_pinning_start">App pinned</string>
+ <string name="screen_pinning_exit">App unpinned</string>
<!-- Hide quick settings tile confirmation title -->
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index ecd8b45..15eda06 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -15,7 +15,7 @@
*/
package com.android.systemui.bubbles;
-
+import static android.app.Notification.FLAG_BUBBLE;
import static android.os.AsyncTask.Status.FINISHED;
import static android.view.Display.INVALID_DISPLAY;
@@ -27,6 +27,7 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
@@ -55,6 +56,11 @@
class Bubble implements BubbleViewProvider {
private static final String TAG = "Bubble";
+ /**
+ * NotificationEntry associated with the bubble. A null value implies this bubble is loaded
+ * from disk.
+ */
+ @Nullable
private NotificationEntry mEntry;
private final String mKey;
@@ -96,11 +102,18 @@
private Bitmap mBadgedImage;
private int mDotColor;
private Path mDotPath;
+ private int mFlags;
- // TODO: Decouple Bubble from NotificationEntry and transform ShortcutInfo into Bubble
- Bubble(ShortcutInfo shortcutInfo) {
+ /**
+ * Create a bubble with limited information based on given {@link ShortcutInfo}.
+ * Note: Currently this is only being used when the bubble is persisted to disk.
+ */
+ Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(shortcutInfo);
mShortcutInfo = shortcutInfo;
- mKey = shortcutInfo.getId();
+ mKey = key;
+ mFlags = 0;
}
/** Used in tests when no UI is required. */
@@ -111,6 +124,7 @@
mKey = e.getKey();
mLastUpdated = e.getSbn().getPostTime();
mSuppressionListener = listener;
+ mFlags = e.getSbn().getNotification().flags;
}
@Override
@@ -118,12 +132,22 @@
return mKey;
}
+ @Nullable
public NotificationEntry getEntry() {
return mEntry;
}
+ @Nullable
+ public UserHandle getUser() {
+ if (mEntry != null) return mEntry.getSbn().getUser();
+ if (mShortcutInfo != null) return mShortcutInfo.getUserHandle();
+ return null;
+ }
+
public String getPackageName() {
- return mEntry.getSbn().getPackageName();
+ return mEntry == null
+ ? mShortcutInfo == null ? null : mShortcutInfo.getPackage()
+ : mEntry.getSbn().getPackageName();
}
@Override
@@ -167,6 +191,18 @@
return mExpandedView;
}
+ @Nullable
+ public String getTitle() {
+ final CharSequence titleCharSeq;
+ if (mEntry == null) {
+ titleCharSeq = null;
+ } else {
+ titleCharSeq = mEntry.getSbn().getNotification().extras.getCharSequence(
+ Notification.EXTRA_TITLE);
+ }
+ return titleCharSeq != null ? titleCharSeq.toString() : null;
+ }
+
/**
* Call when the views should be removed, ensure this is called to clean up ActivityView
* content.
@@ -207,7 +243,8 @@
void inflate(BubbleViewInfoTask.Callback callback,
Context context,
BubbleStackView stackView,
- BubbleIconFactory iconFactory) {
+ BubbleIconFactory iconFactory,
+ boolean skipInflation) {
if (isBubbleLoading()) {
mInflationTask.cancel(true /* mayInterruptIfRunning */);
}
@@ -215,6 +252,7 @@
context,
stackView,
iconFactory,
+ skipInflation,
callback);
if (mInflateSynchronously) {
mInflationTask.onPostExecute(mInflationTask.doInBackground());
@@ -327,6 +365,7 @@
* Whether this notification should be shown in the shade.
*/
boolean showInShade() {
+ if (mEntry == null) return false;
return !shouldSuppressNotification() || !mEntry.isClearable();
}
@@ -334,8 +373,8 @@
* Sets whether this notification should be suppressed in the shade.
*/
void setSuppressNotification(boolean suppressNotification) {
+ if (mEntry == null) return;
boolean prevShowInShade = showInShade();
-
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
int flags = data.getFlags();
if (suppressNotification) {
@@ -366,6 +405,7 @@
*/
@Override
public boolean showDot() {
+ if (mEntry == null) return false;
return mShowBubbleUpdateDot
&& !mEntry.shouldSuppressNotificationDot()
&& !shouldSuppressNotification();
@@ -375,6 +415,7 @@
* Whether the flyout for the bubble should be shown.
*/
boolean showFlyout() {
+ if (mEntry == null) return false;
return !mSuppressFlyout && !mEntry.shouldSuppressPeek()
&& !shouldSuppressNotification()
&& !mEntry.shouldSuppressNotificationList();
@@ -394,6 +435,7 @@
}
float getDesiredHeight(Context context) {
+ if (mEntry == null) return 0;
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
boolean useRes = data.getDesiredHeightResId() != 0;
if (useRes) {
@@ -407,6 +449,7 @@
}
String getDesiredHeightString() {
+ if (mEntry == null) return String.valueOf(0);
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
boolean useRes = data.getDesiredHeightResId() != 0;
if (useRes) {
@@ -423,11 +466,13 @@
* To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}.
*/
boolean usingShortcutInfo() {
- return mEntry.getBubbleMetadata().getShortcutId() != null;
+ return mEntry != null && mEntry.getBubbleMetadata().getShortcutId() != null
+ || mShortcutInfo != null;
}
@Nullable
PendingIntent getBubbleIntent() {
+ if (mEntry == null) return null;
Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
if (data != null) {
return data.getIntent();
@@ -435,16 +480,32 @@
return null;
}
- Intent getSettingsIntent() {
+ Intent getSettingsIntent(final Context context) {
final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
- intent.putExtra(Settings.EXTRA_APP_UID, mEntry.getSbn().getUid());
+ final int uid = getUid(context);
+ if (uid != -1) {
+ intent.putExtra(Settings.EXTRA_APP_UID, uid);
+ }
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
return intent;
}
+ private int getUid(final Context context) {
+ if (mEntry != null) return mEntry.getSbn().getUid();
+ final PackageManager pm = context.getPackageManager();
+ if (pm == null) return -1;
+ try {
+ final ApplicationInfo info = pm.getApplicationInfo(mShortcutInfo.getPackage(), 0);
+ return info.uid;
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "cannot find uid", e);
+ }
+ return -1;
+ }
+
private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) {
PackageManager pm = context.getPackageManager();
Resources r;
@@ -466,11 +527,13 @@
}
private boolean shouldSuppressNotification() {
+ if (mEntry == null) return false;
return mEntry.getBubbleMetadata() != null
&& mEntry.getBubbleMetadata().isNotificationSuppressed();
}
boolean shouldAutoExpand() {
+ if (mEntry == null) return false;
Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata();
return (metadata != null && metadata.getAutoExpandBubble()) || mShouldAutoExpand;
}
@@ -479,6 +542,19 @@
mShouldAutoExpand = shouldAutoExpand;
}
+ public boolean isBubble() {
+ if (mEntry == null) return (mFlags & FLAG_BUBBLE) != 0;
+ return (mEntry.getSbn().getNotification().flags & FLAG_BUBBLE) != 0;
+ }
+
+ public void enable(int option) {
+ mFlags |= option;
+ }
+
+ public void disable(int option) {
+ mFlags &= ~option;
+ }
+
@Override
public String toString() {
return "Bubble{" + mKey + '}';
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 5f157c1..d447596 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -42,6 +42,7 @@
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;
+import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.INotificationManager;
@@ -113,6 +114,7 @@
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
/**
* Bubbles are a special type of content that can "float" on top of other apps or System UI.
@@ -243,13 +245,13 @@
* This can happen when an app cancels a bubbled notification or when the user dismisses a
* bubble.
*/
- void removeNotification(NotificationEntry entry, int reason);
+ void removeNotification(@NonNull NotificationEntry entry, int reason);
/**
* Called when a bubbled notification has changed whether it should be
* filtered from the shade.
*/
- void invalidateNotifications(String reason);
+ void invalidateNotifications(@NonNull String reason);
/**
* Called on a bubbled entry that has been removed when there are no longer
@@ -259,7 +261,7 @@
* removes all remnants of the group's summary from the notification pipeline.
* TODO: (b/145659174) Only old pipeline needs this - delete post-migration.
*/
- void maybeCancelSummary(NotificationEntry entry);
+ void maybeCancelSummary(@NonNull NotificationEntry entry);
}
/**
@@ -755,10 +757,12 @@
mBubbleIconFactory = new BubbleIconFactory(mContext);
// Reload each bubble
for (Bubble b: mBubbleData.getBubbles()) {
- b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory);
+ b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory,
+ false /* skipInflation */);
}
for (Bubble b: mBubbleData.getOverflowBubbles()) {
- b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory);
+ b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory,
+ false /* skipInflation */);
}
}
@@ -845,7 +849,7 @@
void promoteBubbleFromOverflow(Bubble bubble) {
bubble.setInflateSynchronously(mInflateSynchronously);
- setIsBubble(bubble.getEntry(), /* isBubble */ true);
+ setIsBubble(bubble, /* isBubble */ true);
mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory);
}
@@ -895,10 +899,30 @@
updateBubble(notif, false /* suppressFlyout */, true /* showInShade */);
}
+ /**
+ * Fills the overflow bubbles by loading them from disk.
+ */
+ void loadOverflowBubblesFromDisk() {
+ if (!mBubbleData.getOverflowBubbles().isEmpty()) {
+ // we don't need to load overflow bubbles from disk if it is already in memory
+ return;
+ }
+ mDataRepository.loadBubbles((bubbles) -> {
+ bubbles.forEach(bubble -> {
+ if (mBubbleData.getBubbles().contains(bubble)) {
+ // if the bubble is already active, there's no need to push it to overflow
+ return;
+ }
+ bubble.inflate((b) -> mBubbleData.overflowBubble(DISMISS_AGED, bubble),
+ mContext, mStackView, mBubbleIconFactory, true /* skipInflation */);
+ });
+ return null;
+ });
+ }
+
void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) {
// Lazy init stack view when a bubble is created
ensureStackViewCreated();
-
// If this is an interruptive notif, mark that it's interrupted
if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) {
notif.setInterruption();
@@ -918,11 +942,11 @@
return;
}
mHandler.post(
- () -> removeBubble(bubble.getEntry(),
+ () -> removeBubble(bubble.getKey(),
BubbleController.DISMISS_INVALID_INTENT));
});
},
- mContext, mStackView, mBubbleIconFactory);
+ mContext, mStackView, mBubbleIconFactory, false /* skipInflation */);
}
/**
@@ -934,7 +958,10 @@
* @param entry the notification to change bubble state for.
* @param shouldBubble whether the notification should show as a bubble or not.
*/
- public void onUserChangedBubble(NotificationEntry entry, boolean shouldBubble) {
+ public void onUserChangedBubble(@Nullable final NotificationEntry entry, boolean shouldBubble) {
+ if (entry == null) {
+ return;
+ }
NotificationChannel channel = entry.getChannel();
final String appPkg = entry.getSbn().getPackageName();
final int appUid = entry.getSbn().getUid();
@@ -973,14 +1000,14 @@
}
/**
- * Removes the bubble with the given NotificationEntry.
+ * Removes the bubble with the given key.
* <p>
* Must be called from the main thread.
*/
@MainThread
- void removeBubble(NotificationEntry entry, int reason) {
- if (mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
- mBubbleData.notificationEntryRemoved(entry, reason);
+ void removeBubble(String key, int reason) {
+ if (mBubbleData.hasAnyBubbleWithKey(key)) {
+ mBubbleData.notificationEntryRemoved(key, reason);
}
}
@@ -998,7 +1025,7 @@
&& canLaunchInActivityView(mContext, entry);
if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) {
// It was previously a bubble but no longer a bubble -- lets remove it
- removeBubble(entry, DISMISS_NO_LONGER_BUBBLE);
+ removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE);
} else if (shouldBubble && entry.isBubble()) {
updateBubble(entry);
}
@@ -1012,10 +1039,10 @@
// Remove any associated bubble children with the summary
final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey);
for (int i = 0; i < bubbleChildren.size(); i++) {
- removeBubble(bubbleChildren.get(i).getEntry(), DISMISS_GROUP_CANCELLED);
+ removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED);
}
} else {
- removeBubble(entry, DISMISS_NOTIF_CANCEL);
+ removeBubble(entry.getKey(), DISMISS_NOTIF_CANCEL);
}
}
@@ -1037,7 +1064,8 @@
rankingMap.getRanking(key, mTmpRanking);
boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key);
if (isActiveBubble && !mTmpRanking.canBubble()) {
- mBubbleData.notificationEntryRemoved(entry, BubbleController.DISMISS_BLOCKED);
+ mBubbleData.notificationEntryRemoved(entry.getKey(),
+ BubbleController.DISMISS_BLOCKED);
} else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) {
entry.setFlagBubble(true);
onEntryUpdated(entry);
@@ -1045,7 +1073,8 @@
}
}
- private void setIsBubble(NotificationEntry entry, boolean isBubble) {
+ private void setIsBubble(@NonNull final NotificationEntry entry, final boolean isBubble) {
+ Objects.requireNonNull(entry);
if (isBubble) {
entry.getSbn().getNotification().flags |= FLAG_BUBBLE;
} else {
@@ -1058,11 +1087,31 @@
}
}
+ private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) {
+ Objects.requireNonNull(b);
+ if (isBubble) {
+ b.enable(FLAG_BUBBLE);
+ } else {
+ b.disable(FLAG_BUBBLE);
+ }
+ if (b.getEntry() != null) {
+ setIsBubble(b.getEntry(), isBubble);
+ } else {
+ try {
+ mBarService.onNotificationBubbleChanged(b.getKey(), isBubble, 0);
+ } catch (RemoteException e) {
+ // Bad things have happened
+ }
+ }
+ }
+
@SuppressWarnings("FieldCanBeLocal")
private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() {
@Override
public void applyUpdate(BubbleData.Update update) {
+ // Lazy load overflow bubbles from disk
+ loadOverflowBubblesFromDisk();
// Update bubbles in overflow.
if (mOverflowCallback != null) {
mOverflowCallback.run();
@@ -1097,23 +1146,27 @@
// The bubble is now gone & the notification is hidden from the shade, so
// time to actually remove it
for (NotifCallback cb : mCallbacks) {
- cb.removeNotification(bubble.getEntry(), REASON_CANCEL);
+ if (bubble.getEntry() != null) {
+ cb.removeNotification(bubble.getEntry(), REASON_CANCEL);
+ }
}
} else {
- if (bubble.getEntry().isBubble() && bubble.showInShade()) {
- setIsBubble(bubble.getEntry(), false /* isBubble */);
+ if (bubble.isBubble() && bubble.showInShade()) {
+ setIsBubble(bubble, false /* isBubble */);
}
- if (bubble.getEntry().getRow() != null) {
+ if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) {
bubble.getEntry().getRow().updateBubbleButton();
}
}
}
- final String groupKey = bubble.getEntry().getSbn().getGroupKey();
- if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
- // Time to potentially remove the summary
- for (NotifCallback cb : mCallbacks) {
- cb.maybeCancelSummary(bubble.getEntry());
+ if (bubble.getEntry() != null) {
+ final String groupKey = bubble.getEntry().getSbn().getGroupKey();
+ if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) {
+ // Time to potentially remove the summary
+ for (NotifCallback cb : mCallbacks) {
+ cb.maybeCancelSummary(bubble.getEntry());
+ }
}
}
}
@@ -1138,7 +1191,7 @@
if (update.selectionChanged) {
mStackView.setSelectedBubble(update.selectedBubble);
- if (update.selectedBubble != null) {
+ if (update.selectedBubble != null && update.selectedBubble.getEntry() != null) {
mNotificationGroupManager.updateSuppression(
update.selectedBubble.getEntry());
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 35647b0..857f1dd 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -21,6 +21,7 @@
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import android.annotation.NonNull;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
@@ -214,7 +215,7 @@
notificationEntryUpdated(bubble, false /* suppressFlyout */,
true /* showInShade */);
},
- mContext, stack, factory);
+ mContext, stack, factory, false /* skipInflation */);
}
void setShowingOverflow(boolean showingOverflow) {
@@ -268,7 +269,8 @@
}
mPendingBubbles.remove(bubble); // No longer pending once we're here
Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey());
- suppressFlyout |= !bubble.getEntry().getRanking().visuallyInterruptive();
+ suppressFlyout |= bubble.getEntry() == null
+ || !bubble.getEntry().getRanking().visuallyInterruptive();
if (prevBubble == null) {
// Create a new bubble
@@ -297,11 +299,14 @@
dispatchPendingChanges();
}
- public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) {
+ /**
+ * Called when a notification associated with a bubble is removed.
+ */
+ public void notificationEntryRemoved(String key, @DismissReason int reason) {
if (DEBUG_BUBBLE_DATA) {
- Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason);
+ Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason);
}
- doRemove(entry.getKey(), reason);
+ doRemove(key, reason);
dispatchPendingChanges();
}
@@ -349,7 +354,7 @@
return bubbleChildren;
}
for (Bubble b : mBubbles) {
- if (groupKey.equals(b.getEntry().getSbn().getGroupKey())) {
+ if (b.getEntry() != null && groupKey.equals(b.getEntry().getSbn().getGroupKey())) {
bubbleChildren.add(b);
}
}
@@ -447,7 +452,9 @@
Bubble newSelected = mBubbles.get(newIndex);
setSelectedBubbleInternal(newSelected);
}
- maybeSendDeleteIntent(reason, bubbleToRemove.getEntry());
+ if (bubbleToRemove.getEntry() != null) {
+ maybeSendDeleteIntent(reason, bubbleToRemove.getEntry());
+ }
}
void overflowBubble(@DismissReason int reason, Bubble bubble) {
@@ -615,7 +622,8 @@
return true;
}
- private void maybeSendDeleteIntent(@DismissReason int reason, NotificationEntry entry) {
+ private void maybeSendDeleteIntent(@DismissReason int reason,
+ @NonNull final NotificationEntry entry) {
if (reason == BubbleController.DISMISS_USER_GESTURE) {
Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata();
PendingIntent deleteIntent = bubbleMetadata != null
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
index ba93f41..1c5e98b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt
@@ -74,8 +74,10 @@
private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> {
return bubbles.mapNotNull { b ->
- val shortcutId = b.shortcutInfo?.id ?: return@mapNotNull null
- BubbleEntity(userId, b.packageName, shortcutId)
+ var shortcutId = b.shortcutInfo?.id
+ if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId
+ if (shortcutId == null) return@mapNotNull null
+ BubbleEntity(userId, b.packageName, shortcutId, b.key)
}
}
@@ -108,7 +110,6 @@
/**
* Load bubbles from disk.
*/
- // TODO: call this method from BubbleController and update UI
@SuppressLint("WrongConstant")
fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch {
/**
@@ -132,17 +133,17 @@
val shortcutKeys = entities.map { ShortcutKey(it.userId, it.packageName) }.toSet()
/**
* Retrieve shortcuts with given userId/packageName combination, then construct a mapping
- * between BubbleEntity and ShortcutInfo.
+ * from the userId/packageName pair to a list of associated ShortcutInfo.
* e.g.
* {
- * BubbleEntity(0, "com.example.messenger", "id-0") ->
+ * ShortcutKey(0, "com.example.messenger") -> [
* ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-0"),
- * BubbleEntity(0, "com.example.messenger", "id-2") ->
- * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-2"),
- * BubbleEntity(10, "com.example.chat", "id-1") ->
+ * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-2")
+ * ]
+ * ShortcutKey(10, "com.example.chat") -> [
* ShortcutInfo(userId=10, pkg="com.example.chat", id="id-1"),
- * BubbleEntity(10, "com.example.chat", "id-3") ->
* ShortcutInfo(userId=10, pkg="com.example.chat", id="id-3")
+ * ]
* }
*/
val shortcutMap = shortcutKeys.flatMap { key ->
@@ -150,11 +151,15 @@
LauncherApps.ShortcutQuery()
.setPackage(key.pkg)
.setQueryFlags(SHORTCUT_QUERY_FLAG), UserHandle.of(key.userId))
- ?.map { BubbleEntity(key.userId, key.pkg, it.id) to it } ?: emptyList()
- }.toMap()
+ ?: emptyList()
+ }.groupBy { ShortcutKey(it.userId, it.`package`) }
// For each entity loaded from xml, find the corresponding ShortcutInfo then convert them
// into Bubble.
- val bubbles = entities.mapNotNull { entity -> shortcutMap[entity]?.let { Bubble(it) } }
+ val bubbles = entities.mapNotNull { entity ->
+ shortcutMap[ShortcutKey(entity.userId, entity.packageName)]
+ ?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id }
+ ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo) }
+ }
uiScope.launch { cb(bubbles) }
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index c4b4f43..494a51d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -65,7 +65,6 @@
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
import com.android.systemui.statusbar.AlphaOptimizedButton;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* Container for the expanded bubble view, handles rendering the caret and settings icon.
@@ -161,7 +160,7 @@
// the bubble again so we'll just remove it.
Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
+ ", " + e.getMessage() + "; removing bubble");
- mBubbleController.removeBubble(getBubbleEntry(),
+ mBubbleController.removeBubble(getBubbleKey(),
BubbleController.DISMISS_INVALID_INTENT);
}
});
@@ -205,7 +204,7 @@
}
if (mBubble != null) {
// Must post because this is called from a binder thread.
- post(() -> mBubbleController.removeBubble(mBubble.getEntry(),
+ post(() -> mBubbleController.removeBubble(mBubble.getKey(),
BubbleController.DISMISS_TASK_FINISHED));
}
}
@@ -297,10 +296,6 @@
return mBubble != null ? mBubble.getKey() : "null";
}
- private NotificationEntry getBubbleEntry() {
- return mBubble != null ? mBubble.getEntry() : null;
- }
-
void setManageClickListener(OnClickListener manageClickListener) {
findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 8fec338..2109a7b 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -21,7 +21,6 @@
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import android.app.Activity;
-import android.app.Notification;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -260,12 +259,9 @@
mPromoteBubbleFromOverflow.accept(b);
});
- final CharSequence titleCharSeq =
- b.getEntry().getSbn().getNotification().extras.getCharSequence(
- Notification.EXTRA_TITLE);
- String titleStr = mContext.getResources().getString(R.string.notification_bubble_title);
- if (titleCharSeq != null) {
- titleStr = titleCharSeq.toString();
+ String titleStr = b.getTitle();
+ if (titleStr == null) {
+ titleStr = mContext.getResources().getString(R.string.notification_bubble_title);
}
vh.iconView.setContentDescription(mContext.getResources().getString(
R.string.bubble_content_description_single, titleStr, b.getAppName()));
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 418cc50..6ba1aa8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -31,7 +31,6 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
-import android.app.Notification;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
@@ -50,7 +49,6 @@
import android.graphics.Region;
import android.os.Bundle;
import android.provider.Settings;
-import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.Choreographer;
import android.view.DisplayCutout;
@@ -941,10 +939,10 @@
showManageMenu(false /* show */);
final Bubble bubble = mBubbleData.getSelectedBubble();
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
- final Intent intent = bubble.getSettingsIntent();
+ final Intent intent = bubble.getSettingsIntent(mContext);
collapseStack(() -> {
- mContext.startActivityAsUser(
- intent, bubble.getEntry().getSbn().getUser());
+
+ mContext.startActivityAsUser(intent, bubble.getUser());
logBubbleClickEvent(
bubble,
SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
@@ -1202,13 +1200,10 @@
for (int i = 0; i < mBubbleData.getBubbles().size(); i++) {
final Bubble bubble = mBubbleData.getBubbles().get(i);
final String appName = bubble.getAppName();
- final Notification notification = bubble.getEntry().getSbn().getNotification();
- final CharSequence titleCharSeq =
- notification.extras.getCharSequence(Notification.EXTRA_TITLE);
- String titleStr = getResources().getString(R.string.notification_bubble_title);
- if (titleCharSeq != null) {
- titleStr = titleCharSeq.toString();
+ String titleStr = bubble.getTitle();
+ if (titleStr == null) {
+ titleStr = getResources().getString(R.string.notification_bubble_title);
}
if (bubble.getIconView() != null) {
@@ -1821,7 +1816,7 @@
private void dismissBubbleIfExists(@Nullable Bubble bubble) {
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
mBubbleData.notificationEntryRemoved(
- bubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ bubble.getKey(), BubbleController.DISMISS_USER_GESTURE);
}
}
@@ -2319,18 +2314,12 @@
* @param action the user interaction enum.
*/
private void logBubbleClickEvent(Bubble bubble, int action) {
- StatusBarNotification notification = bubble.getEntry().getSbn();
- SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
- notification.getPackageName(),
- notification.getNotification().getChannelId(),
- notification.getId(),
- getBubbleIndex(getExpandedBubble()),
+ bubble.logUIEvent(
getBubbleCount(),
action,
getNormalizedXPosition(),
getNormalizedYPosition(),
- bubble.showInShade(),
- false /* isOngoing (unused) */,
- false /* isAppForeground (unused) */);
+ getBubbleIndex(getExpandedBubble())
+ );
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index 8a57a73..525d5b5 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -37,6 +37,7 @@
import android.graphics.drawable.Icon;
import android.os.AsyncTask;
import android.os.Parcelable;
+import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.text.TextUtils;
import android.util.Log;
@@ -74,6 +75,7 @@
private WeakReference<Context> mContext;
private WeakReference<BubbleStackView> mStackView;
private BubbleIconFactory mIconFactory;
+ private boolean mSkipInflation;
private Callback mCallback;
/**
@@ -84,17 +86,20 @@
Context context,
BubbleStackView stackView,
BubbleIconFactory factory,
+ boolean skipInflation,
Callback c) {
mBubble = b;
mContext = new WeakReference<>(context);
mStackView = new WeakReference<>(stackView);
mIconFactory = factory;
+ mSkipInflation = skipInflation;
mCallback = c;
}
@Override
protected BubbleViewInfo doInBackground(Void... voids) {
- return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble);
+ return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble,
+ mSkipInflation);
}
@Override
@@ -123,11 +128,36 @@
@Nullable
static BubbleViewInfo populate(Context c, BubbleStackView stackView,
- BubbleIconFactory iconFactory, Bubble b) {
+ BubbleIconFactory iconFactory, Bubble b, boolean skipInflation) {
+ final NotificationEntry entry = b.getEntry();
+ if (entry == null) {
+ // populate from ShortcutInfo when NotificationEntry is not available
+ final ShortcutInfo s = b.getShortcutInfo();
+ return populate(c, stackView, iconFactory, skipInflation || b.isInflated(),
+ s.getPackage(), s.getUserHandle(), s, null);
+ }
+ final StatusBarNotification sbn = entry.getSbn();
+ final String bubbleShortcutId = entry.getBubbleMetadata().getShortcutId();
+ final ShortcutInfo si = bubbleShortcutId == null
+ ? null : entry.getRanking().getShortcutInfo();
+ return populate(
+ c, stackView, iconFactory, skipInflation || b.isInflated(),
+ sbn.getPackageName(), sbn.getUser(), si, entry);
+ }
+
+ private static BubbleViewInfo populate(
+ @NonNull final Context c,
+ @NonNull final BubbleStackView stackView,
+ @NonNull final BubbleIconFactory iconFactory,
+ final boolean isInflated,
+ @NonNull final String packageName,
+ @NonNull final UserHandle user,
+ @Nullable final ShortcutInfo shortcutInfo,
+ @Nullable final NotificationEntry entry) {
BubbleViewInfo info = new BubbleViewInfo();
// View inflation: only should do this once per bubble
- if (!b.isInflated()) {
+ if (!isInflated) {
LayoutInflater inflater = LayoutInflater.from(c);
info.imageView = (BadgedImageView) inflater.inflate(
R.layout.bubble_view, stackView, false /* attachToRoot */);
@@ -137,12 +167,8 @@
info.expandedView.setStackView(stackView);
}
- StatusBarNotification sbn = b.getEntry().getSbn();
- String packageName = sbn.getPackageName();
-
- String bubbleShortcutId = b.getEntry().getBubbleMetadata().getShortcutId();
- if (bubbleShortcutId != null) {
- info.shortcutInfo = b.getEntry().getRanking().getShortcutInfo();
+ if (shortcutInfo != null) {
+ info.shortcutInfo = shortcutInfo;
}
// App name & app icon
@@ -161,7 +187,7 @@
info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
}
appIcon = pm.getApplicationIcon(packageName);
- badgedIcon = pm.getUserBadgedIcon(appIcon, sbn.getUser());
+ badgedIcon = pm.getUserBadgedIcon(appIcon, user);
} catch (PackageManager.NameNotFoundException exception) {
// If we can't find package... don't think we should show the bubble.
Log.w(TAG, "Unable to find package: " + packageName);
@@ -170,7 +196,7 @@
// Badged bubble image
Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
- b.getEntry().getBubbleMetadata());
+ entry == null ? null : entry.getBubbleMetadata());
if (bubbleDrawable == null) {
// Default to app icon
bubbleDrawable = appIcon;
@@ -196,7 +222,9 @@
Color.WHITE, WHITE_SCRIM_ALPHA);
// Flyout
- info.flyoutMessage = extractFlyoutMessage(c, b.getEntry());
+ if (entry != null) {
+ info.flyoutMessage = extractFlyoutMessage(c, entry);
+ }
return info;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt
index 4690a8e..4348261 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt
@@ -20,5 +20,6 @@
data class BubbleEntity(
@UserIdInt val userId: Int,
val packageName: String,
- val shortcutId: String
+ val shortcutId: String,
+ val key: String
)
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt
index 821b64c..1df9f72 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt
@@ -30,6 +30,7 @@
private const val ATTR_USER_ID = "uid"
private const val ATTR_PACKAGE = "pkg"
private const val ATTR_SHORTCUT_ID = "sid"
+private const val ATTR_KEY = "key"
/**
* Writes the bubbles in xml format into given output stream.
@@ -48,7 +49,7 @@
/**
* Creates a xml entry for given bubble in following format:
* ```
- * <bb uid="0" pkg="com.example.messenger" sid="my-shortcut" />
+ * <bb uid="0" pkg="com.example.messenger" sid="my-shortcut" key="my-key" />
* ```
*/
private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) {
@@ -57,6 +58,7 @@
serializer.attribute(null, ATTR_USER_ID, bubble.userId.toString())
serializer.attribute(null, ATTR_PACKAGE, bubble.packageName)
serializer.attribute(null, ATTR_SHORTCUT_ID, bubble.shortcutId)
+ serializer.attribute(null, ATTR_KEY, bubble.key)
serializer.endTag(null, TAG_BUBBLE)
} catch (e: IOException) {
throw RuntimeException(e)
@@ -83,7 +85,8 @@
return BubbleEntity(
parser.getAttributeWithName(ATTR_USER_ID)?.toInt() ?: return null,
parser.getAttributeWithName(ATTR_PACKAGE) ?: return null,
- parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null
+ parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null,
+ parser.getAttributeWithName(ATTR_KEY) ?: return null
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
new file mode 100644
index 0000000..9a5b960
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.dagger
+
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsUiController
+import dagger.Lazy
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Pseudo-component to inject into classes outside `com.android.systemui.controls`.
+ *
+ * If `featureEnabled` is false, all the optionals should be empty. The controllers will only be
+ * instantiated if `featureEnabled` is true.
+ */
+@Singleton
+class ControlsComponent @Inject constructor(
+ @ControlsFeatureEnabled private val featureEnabled: Boolean,
+ private val lazyControlsController: Lazy<ControlsController>,
+ private val lazyControlsUiController: Lazy<ControlsUiController>,
+ private val lazyControlsListingController: Lazy<ControlsListingController>
+) {
+ fun getControlsController(): Optional<ControlsController> {
+ return if (featureEnabled) Optional.of(lazyControlsController.get()) else Optional.empty()
+ }
+
+ fun getControlsUiController(): Optional<ControlsUiController> {
+ return if (featureEnabled) Optional.of(lazyControlsUiController.get()) else Optional.empty()
+ }
+
+ fun getControlsListingController(): Optional<ControlsListingController> {
+ return if (featureEnabled) {
+ Optional.of(lazyControlsListingController.get())
+ } else {
+ Optional.empty()
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsFeatureEnabled.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsFeatureEnabled.kt
new file mode 100644
index 0000000..dd061c5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsFeatureEnabled.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.dagger
+
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class ControlsFeatureEnabled
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
index 5765be5..4760d29 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -17,6 +17,7 @@
package com.android.systemui.controls.dagger
import android.app.Activity
+import android.content.pm.PackageManager
import com.android.systemui.controls.controller.ControlsBindingController
import com.android.systemui.controls.controller.ControlsBindingControllerImpl
import com.android.systemui.controls.controller.ControlsController
@@ -28,19 +29,39 @@
import com.android.systemui.controls.management.ControlsListingControllerImpl
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.management.ControlsRequestDialog
-import com.android.systemui.controls.ui.ControlsUiController
-import com.android.systemui.controls.ui.ControlsUiControllerImpl
import com.android.systemui.controls.ui.ControlActionCoordinator
import com.android.systemui.controls.ui.ControlActionCoordinatorImpl
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.ControlsUiControllerImpl
import dagger.Binds
import dagger.BindsOptionalOf
import dagger.Module
+import dagger.Provides
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
+import javax.inject.Singleton
+/**
+ * Module for injecting classes in `com.android.systemui.controls`-
+ *
+ * Classes provided by this module should only be injected directly into other classes in this
+ * module. For injecting outside of this module (for example, [GlobalActionsDialog], inject
+ * [ControlsComponent] and obtain the corresponding optionals from it.
+ */
@Module
abstract class ControlsModule {
+ @Module
+ companion object {
+ @JvmStatic
+ @Provides
+ @Singleton
+ @ControlsFeatureEnabled
+ fun providesControlsFeatureEnabled(pm: PackageManager): Boolean {
+ return pm.hasSystemFeature(PackageManager.FEATURE_CONTROLS)
+ }
+ }
+
@Binds
abstract fun provideControlsListingController(
controller: ControlsListingControllerImpl
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt
index 0d23557..bf84d77 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt
@@ -55,6 +55,9 @@
}
override fun onReceive(context: Context, intent: Intent) {
+ if (!context.packageManager.hasSystemFeature(PackageManager.FEATURE_CONTROLS)) {
+ return
+ }
val packageName = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
?.packageName
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index ab33291..606e947 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -60,7 +60,6 @@
import com.android.systemui.globalactions.GlobalActionsPopupMenu
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.ShadeController
-import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import dagger.Lazy
import java.text.Collator
@@ -80,7 +79,6 @@
@Main val sharedPreferences: SharedPreferences,
val controlActionCoordinator: ControlActionCoordinator,
private val activityStarter: ActivityStarter,
- private val keyguardStateController: KeyguardStateController,
private val shadeController: ShadeController
) : ControlsUiController {
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 61c9a96..d66b9ac 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -120,8 +120,8 @@
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.controller.ControlsController;
+import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsAnimations;
-import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsUiController;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -140,6 +140,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -234,11 +235,12 @@
private final IStatusBarService mStatusBarService;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private GlobalActionsPanelPlugin mWalletPlugin;
- private ControlsUiController mControlsUiController;
+ private Optional<ControlsUiController> mControlsUiControllerOptional;
private final IWindowManager mIWindowManager;
private final Executor mBackgroundExecutor;
private List<ControlsServiceInfo> mControlsServiceInfos = new ArrayList<>();
- private ControlsController mControlsController;
+ private Optional<ControlsController> mControlsControllerOptional;
+ private SharedPreferences mControlsPreferences;
private final RingerModeTracker mRingerModeTracker;
private int mDialogPressDelay = DIALOG_PRESS_DELAY; // ms
private Handler mMainHandler;
@@ -298,11 +300,11 @@
NotificationShadeDepthController depthController, SysuiColorExtractor colorExtractor,
IStatusBarService statusBarService,
NotificationShadeWindowController notificationShadeWindowController,
- ControlsUiController controlsUiController, IWindowManager iWindowManager,
+ IWindowManager iWindowManager,
@Background Executor backgroundExecutor,
- ControlsListingController controlsListingController,
- ControlsController controlsController, UiEventLogger uiEventLogger,
+ UiEventLogger uiEventLogger,
RingerModeTracker ringerModeTracker, SysUiState sysUiState, @Main Handler handler,
+ ControlsComponent controlsComponent,
CurrentUserContextTracker currentUserContextTracker) {
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
mWindowManagerFuncs = windowManagerFuncs;
@@ -325,11 +327,11 @@
mSysuiColorExtractor = colorExtractor;
mStatusBarService = statusBarService;
mNotificationShadeWindowController = notificationShadeWindowController;
- mControlsUiController = controlsUiController;
+ mControlsUiControllerOptional = controlsComponent.getControlsUiController();
mIWindowManager = iWindowManager;
mBackgroundExecutor = backgroundExecutor;
mRingerModeTracker = ringerModeTracker;
- mControlsController = controlsController;
+ mControlsControllerOptional = controlsComponent.getControlsController();
mSysUiState = sysUiState;
mMainHandler = handler;
mCurrentUserContextTracker = currentUserContextTracker;
@@ -374,7 +376,7 @@
mDialog.mWalletViewController.onDeviceLockStateChanged(!unlocked);
}
if (!mDialog.isShowingControls() && shouldShowControls()) {
- mDialog.showControls(mControlsUiController);
+ mDialog.showControls(mControlsUiControllerOptional.get());
}
if (unlocked) {
mDialog.hideLockMessage();
@@ -383,7 +385,16 @@
}
});
- controlsListingController.addCallback(list -> mControlsServiceInfos = list);
+ if (controlsComponent.getControlsListingController().isPresent()) {
+ controlsComponent.getControlsListingController().get()
+ .addCallback(list -> mControlsServiceInfos = list);
+ }
+
+ // Need to be user-specific with the context to make sure we read the correct prefs
+ Context userContext = context.createContextAsUser(
+ new UserHandle(mUserManager.getUserHandle()), 0);
+ mControlsPreferences = userContext.getSharedPreferences(PREFS_CONTROLS_FILE,
+ Context.MODE_PRIVATE);
// Listen for changes to show controls on the power menu while locked
onPowerMenuLockScreenSettingsChanged();
@@ -399,8 +410,9 @@
}
private void seedFavorites() {
+ if (!mControlsControllerOptional.isPresent()) return;
if (mControlsServiceInfos.isEmpty()
- || mControlsController.getFavorites().size() > 0) {
+ || mControlsControllerOptional.get().getFavorites().size() > 0) {
return;
}
@@ -433,7 +445,7 @@
return;
}
- mControlsController.seedFavoritesForComponent(
+ mControlsControllerOptional.get().seedFavoritesForComponent(
preferredComponent,
(accepted) -> {
Log.i(TAG, "Controls seeded: " + accepted);
@@ -636,10 +648,14 @@
mDepthController.setShowingHomeControls(true);
GlobalActionsPanelPlugin.PanelViewController walletViewController =
getWalletViewController();
+ ControlsUiController uiController = null;
+ if (mControlsUiControllerOptional.isPresent() && shouldShowControls()) {
+ uiController = mControlsUiControllerOptional.get();
+ }
ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, mOverflowAdapter,
walletViewController, mDepthController, mSysuiColorExtractor,
mStatusBarService, mNotificationShadeWindowController,
- controlsAvailable(), shouldShowControls() ? mControlsUiController : null,
+ controlsAvailable(), uiController,
mSysUiState, this::onRotate, mKeyguardShowing);
boolean walletViewAvailable = walletViewController != null
&& walletViewController.getPanelContent() != null;
@@ -2403,7 +2419,8 @@
private boolean controlsAvailable() {
return mDeviceProvisioned
- && mControlsUiController.getAvailable()
+ && mControlsUiControllerOptional.isPresent()
+ && mControlsUiControllerOptional.get().getAvailable()
&& !mControlsServiceInfos.isEmpty();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 009f549..cf7fbfa 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -36,6 +36,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.notification.MediaNotificationProcessor
import com.android.systemui.statusbar.notification.row.HybridGroupManager
+import com.android.systemui.util.Assert
import com.android.systemui.util.Utils
import java.io.IOException
import java.util.concurrent.Executor
@@ -85,6 +86,7 @@
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) {
+ Assert.isMainThread()
if (!mediaEntries.containsKey(key)) {
mediaEntries.put(key, LOADING)
}
@@ -269,19 +271,23 @@
}
fun onMediaDataLoaded(key: String, data: MediaData) {
+ Assert.isMainThread()
if (mediaEntries.containsKey(key)) {
// Otherwise this was removed already
mediaEntries.put(key, data)
- listeners.forEach {
+ val listenersCopy = listeners.toSet()
+ listenersCopy.forEach {
it.onMediaDataLoaded(key, data)
}
}
}
fun onNotificationRemoved(key: String) {
+ Assert.isMainThread()
val removed = mediaEntries.remove(key)
if (removed != null) {
- listeners.forEach {
+ val listenersCopy = listeners.toSet()
+ listenersCopy.forEach {
it.onMediaDataRemoved(key)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
index fe84d818..3874903 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java
@@ -31,6 +31,8 @@
import android.graphics.drawable.ColorDrawable;
import android.os.Binder;
import android.os.RemoteException;
+import android.text.SpannableStringBuilder;
+import android.text.style.BulletSpan;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
@@ -293,8 +295,20 @@
.setImageDrawable(navigationBarView.getHomeDrawable());
}
- ((TextView) mLayout.findViewById(R.id.screen_pinning_description))
- .setText(descriptionStringResId);
+ // Create a bulleted list of the default description plus the two security notes.
+ int gapWidth = getResources().getDimensionPixelSize(
+ R.dimen.screen_pinning_description_bullet_gap_width);
+ SpannableStringBuilder description = new SpannableStringBuilder();
+ description.append(getContext().getText(descriptionStringResId),
+ new BulletSpan(gapWidth), /* flags */ 0);
+ description.append(System.lineSeparator());
+ description.append(getContext().getText(R.string.screen_pinning_exposes_personal_data),
+ new BulletSpan(gapWidth), /* flags */ 0);
+ description.append(System.lineSeparator());
+ description.append(getContext().getText(R.string.screen_pinning_can_open_other_apps),
+ new BulletSpan(gapWidth), /* flags */ 0);
+ ((TextView) mLayout.findViewById(R.id.screen_pinning_description)).setText(description);
+
final int backBgVisibility = touchExplorationEnabled ? View.INVISIBLE : View.VISIBLE;
mLayout.findViewById(R.id.screen_pinning_back_bg).setVisibility(backBgVisibility);
mLayout.findViewById(R.id.screen_pinning_back_bg_light).setVisibility(backBgVisibility);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index f2d2eb3..33d692f 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -431,7 +431,13 @@
data.createDeleteAction = false;
if (mSaveInBgTask != null) {
- mSaveInBgTask.ignoreResult();
+ // just log success/failure for the pre-existing screenshot
+ mSaveInBgTask.setActionsReadyListener(new ActionsReadyListener() {
+ @Override
+ void onActionsReady(SavedImageData imageData) {
+ logSuccessOnActionsReady(imageData);
+ }
+ });
}
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data);
@@ -637,6 +643,52 @@
}
/**
+ * Sets up the action shade and its entrance animation, once we get the screenshot URI.
+ */
+ private void showUiOnActionsReady(SavedImageData imageData) {
+ logSuccessOnActionsReady(imageData);
+ if (imageData.uri != null) {
+ mScreenshotHandler.post(() -> {
+ if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
+ mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ createScreenshotActionsShadeAnimation(imageData).start();
+ }
+ });
+ } else {
+ createScreenshotActionsShadeAnimation(imageData).start();
+ }
+
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis(
+ SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS,
+ AccessibilityManager.FLAG_CONTENT_CONTROLS);
+
+ mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
+ mScreenshotHandler.sendMessageDelayed(
+ mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
+ timeoutMs);
+ });
+ }
+ }
+
+ /**
+ * Logs success/failure of the screenshot saving task, and shows an error if it failed.
+ */
+ private void logSuccessOnActionsReady(SavedImageData imageData) {
+ if (imageData.uri == null) {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ } else {
+ mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
+ }
+ }
+
+ /**
* Starts the animation after taking the screenshot
*/
private void startAnimation(final Consumer<Uri> finisher, int w, int h,
@@ -651,43 +703,11 @@
mScreenshotAnimation = createScreenshotDropInAnimation(w, h, screenRect);
saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
- @Override
- void onActionsReady(SavedImageData imageData) {
- finisher.accept(imageData.uri);
- if (imageData.uri == null) {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED);
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- } else {
- mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED);
- mScreenshotHandler.post(() -> {
- if (mScreenshotAnimation != null && mScreenshotAnimation.isRunning()) {
- mScreenshotAnimation.addListener(
- new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- super.onAnimationEnd(animation);
- createScreenshotActionsShadeAnimation(imageData)
- .start();
- }
- });
- } else {
- createScreenshotActionsShadeAnimation(imageData).start();
- }
- AccessibilityManager accessibilityManager = (AccessibilityManager)
- mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
- long timeoutMs = accessibilityManager.getRecommendedTimeoutMillis(
- SCREENSHOT_CORNER_DEFAULT_TIMEOUT_MILLIS,
- AccessibilityManager.FLAG_CONTENT_CONTROLS);
-
- mScreenshotHandler.removeMessages(MESSAGE_CORNER_TIMEOUT);
- mScreenshotHandler.sendMessageDelayed(
- mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
- timeoutMs);
- });
- }
- }
- });
+ @Override
+ void onActionsReady(SavedImageData imageData) {
+ showUiOnActionsReady(imageData);
+ }
+ });
mScreenshotHandler.post(() -> {
if (!mScreenshotLayout.isAttachedToWindow()) {
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index f0a81e9..a5bab21 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -214,6 +214,7 @@
mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri);
mParams.mActionsReadyListener.onActionsReady(mImageData);
+ mParams.finisher.accept(mImageData.uri);
mParams.image = null;
mParams.errorMsgResId = 0;
} catch (Exception e) {
@@ -224,22 +225,18 @@
mParams.errorMsgResId = R.string.screenshot_failed_to_save_text;
mImageData.reset();
mParams.mActionsReadyListener.onActionsReady(mImageData);
+ mParams.finisher.accept(null);
}
return null;
}
/**
- * If we get a new screenshot request while this one is saving, we want to continue saving in
- * the background but not return anything.
+ * Update the listener run when the saving task completes. Used to avoid showing UI for the
+ * first screenshot when a second one is taken.
*/
- void ignoreResult() {
- mParams.mActionsReadyListener = new GlobalScreenshot.ActionsReadyListener() {
- @Override
- void onActionsReady(GlobalScreenshot.SavedImageData imageData) {
- // do nothing
- }
- };
+ void setActionsReadyListener(GlobalScreenshot.ActionsReadyListener listener) {
+ mParams.mActionsReadyListener = listener;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 1eadd9e..3377144 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -41,6 +41,7 @@
import android.app.NotificationChannel;
import android.app.NotificationManager.Policy;
import android.app.Person;
+import android.app.RemoteInput;
import android.app.RemoteInputHistoryItem;
import android.content.Context;
import android.content.pm.ShortcutInfo;
@@ -69,6 +70,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.stack.PriorityBucket;
+import com.android.systemui.statusbar.phone.NotificationGroupManager;
import java.util.ArrayList;
import java.util.List;
@@ -132,7 +134,7 @@
private ShortcutInfo mShortcutInfo;
/**
- * If {@link android.app.RemoteInput#getEditChoicesBeforeSending} is enabled, and the user is
+ * If {@link RemoteInput#getEditChoicesBeforeSending} is enabled, and the user is
* currently editing a choice (smart reply), then this field contains the information about the
* suggestion being edited. Otherwise <code>null</code>.
*/
@@ -174,6 +176,8 @@
private boolean mPulseSupressed;
private boolean mAllowFgsDismissal;
private int mBucket = BUCKET_ALERTING;
+ @Nullable private Long mPendingAnimationDuration;
+ private boolean mIsMarkedForUserTriggeredMovement;
/**
* @param sbn the StatusBarNotification from system server
@@ -193,7 +197,7 @@
boolean allowFgsDismissal,
long creationTime
) {
- super(requireNonNull(Objects.requireNonNull(sbn).getKey()));
+ super(requireNonNull(requireNonNull(sbn).getKey()));
requireNonNull(ranking);
@@ -441,7 +445,7 @@
* Get the children that are actually attached to this notification's row.
*
* TODO: Seems like most callers here should probably be using
- * {@link com.android.systemui.statusbar.phone.NotificationGroupManager#getChildren}
+ * {@link NotificationGroupManager#getChildren}
*/
public @Nullable List<NotificationEntry> getAttachedNotifChildren() {
if (row == null) {
@@ -809,7 +813,7 @@
}
if ((mSbn.getNotification().flags
- & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
+ & FLAG_FOREGROUND_SERVICE) != 0) {
return true;
}
if (mSbn.getNotification().isMediaNotification()) {
@@ -942,6 +946,19 @@
mPulseSupressed = suppressed;
}
+ /** Whether or not this entry has been marked for a user-triggered movement. */
+ public boolean isMarkedForUserTriggeredMovement() {
+ return mIsMarkedForUserTriggeredMovement;
+ }
+
+ /**
+ * Mark this entry for movement triggered by a user action (ex: changing the priorirty of a
+ * conversation). This can then be used for custom animations.
+ */
+ public void markForUserTriggeredMovement(boolean marked) {
+ mIsMarkedForUserTriggeredMovement = marked;
+ }
+
/** Information about a suggestion that is being edited. */
public static class EditedSuggestionInfo {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index f55ce77..033a638 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -25,6 +25,7 @@
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -105,6 +106,7 @@
VisualStabilityManager visualStabilityManager,
Lazy<StatusBar> statusBarLazy,
@Main Handler mainHandler,
+ @Background Handler bgHandler,
AccessibilityManager accessibilityManager,
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
@@ -118,6 +120,7 @@
visualStabilityManager,
statusBarLazy,
mainHandler,
+ bgHandler,
accessibilityManager,
highPriorityProvider,
notificationManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index e0583be..9217756 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.row;
-import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
@@ -43,9 +42,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
-import android.graphics.drawable.Icon;
import android.os.Handler;
-import android.os.Parcelable;
import android.os.RemoteException;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
@@ -65,15 +62,16 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.notification.ConversationIconFactory;
-import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
import java.lang.annotation.Retention;
-import java.util.List;
import javax.inject.Provider;
@@ -86,10 +84,12 @@
private INotificationManager mINotificationManager;
- ShortcutManager mShortcutManager;
+ private ShortcutManager mShortcutManager;
private PackageManager mPm;
private ConversationIconFactory mIconFactory;
private VisualStabilityManager mVisualStabilityManager;
+ private Handler mMainHandler;
+ private Handler mBgHandler;
private String mPackageName;
private String mAppName;
@@ -97,6 +97,7 @@
private String mDelegatePkg;
private NotificationChannel mNotificationChannel;
private ShortcutInfo mShortcutInfo;
+ private NotificationEntry mEntry;
private StatusBarNotification mSbn;
@Nullable private Notification.BubbleMetadata mBubbleMetadata;
private Context mUserContext;
@@ -213,11 +214,14 @@
ConversationIconFactory conversationIconFactory,
Context userContext,
Provider<PriorityOnboardingDialogController.Builder> builderProvider,
- boolean isDeviceProvisioned) {
+ boolean isDeviceProvisioned,
+ @Main Handler mainHandler,
+ @Background Handler bgHandler) {
mSelectedAction = -1;
mINotificationManager = iNotificationManager;
mVisualStabilityManager = visualStabilityManager;
mPackageName = pkg;
+ mEntry = entry;
mSbn = entry.getSbn();
mPm = pm;
mAppName = mPackageName;
@@ -231,7 +235,8 @@
mUserContext = userContext;
mBubbleMetadata = bubbleMetadata;
mBuilderProvider = builderProvider;
-
+ mMainHandler = mainHandler;
+ mBgHandler = bgHandler;
mShortcutManager = shortcutManager;
mShortcutInfo = entry.getRanking().getShortcutInfo();
if (mShortcutInfo == null) {
@@ -494,11 +499,13 @@
}
private void updateChannel() {
- Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
- bgHandler.post(
+ mBgHandler.post(
new UpdateChannelRunnable(mINotificationManager, mPackageName,
mAppUid, mSelectedAction, mNotificationChannel));
- mVisualStabilityManager.temporarilyAllowReordering();
+ mMainHandler.postDelayed(() -> {
+ mEntry.markForUserTriggeredMovement(true);
+ mVisualStabilityManager.temporarilyAllowReordering();
+ }, StackStateAnimator.ANIMATION_DURATION_STANDARD);
}
private boolean shouldShowPriorityOnboarding() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 3f7c7ca..1caf8f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -46,6 +46,7 @@
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -111,6 +112,7 @@
private final Lazy<StatusBar> mStatusBarLazy;
private final Handler mMainHandler;
+ private final Handler mBgHandler;
private Runnable mOpenRunnable;
private final INotificationManager mNotificationManager;
private final LauncherApps mLauncherApps;
@@ -122,7 +124,7 @@
* Injected constructor. See {@link NotificationsModule}.
*/
public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager,
- Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler,
+ Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler, @Background Handler bgHandler,
AccessibilityManager accessibilityManager,
HighPriorityProvider highPriorityProvider,
INotificationManager notificationManager,
@@ -135,6 +137,7 @@
mVisualStabilityManager = visualStabilityManager;
mStatusBarLazy = statusBarLazy;
mMainHandler = mainHandler;
+ mBgHandler = bgHandler;
mAccessibilityManager = accessibilityManager;
mHighPriorityProvider = highPriorityProvider;
mNotificationManager = notificationManager;
@@ -463,7 +466,9 @@
iconFactoryLoader,
mContextTracker.getCurrentUserContext(),
mBuilderProvider,
- mDeviceProvisionedController.isDeviceProvisioned());
+ mDeviceProvisionedController.isDeviceProvisioned(),
+ mMainHandler,
+ mBgHandler);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index bcafd0ee..a877bc1c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -3655,8 +3655,19 @@
@ShadeViewRefactor(RefactorComponent.STATE_RESOLVER)
private void generatePositionChangeEvents() {
for (ExpandableView child : mChildrenChangingPositions) {
- mAnimationEvents.add(new AnimationEvent(child,
- AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION));
+ Integer duration = null;
+ if (child instanceof ExpandableNotificationRow) {
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (row.getEntry().isMarkedForUserTriggeredMovement()) {
+ duration = StackStateAnimator.ANIMATION_DURATION_PRIORITY_CHANGE;
+ row.getEntry().markForUserTriggeredMovement(false);
+ }
+ }
+ AnimationEvent animEvent = duration == null
+ ? new AnimationEvent(child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION)
+ : new AnimationEvent(
+ child, AnimationEvent.ANIMATION_TYPE_CHANGE_POSITION, duration);
+ mAnimationEvents.add(animEvent);
}
mChildrenChangingPositions.clear();
if (mGenerateChildOrderChangedEvent) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 7785082..d4add95 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -56,6 +56,7 @@
public static final int ANIMATION_DURATION_PULSE_APPEAR =
KeyguardSliceView.DEFAULT_ANIM_DURATION;
public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
+ public static final int ANIMATION_DURATION_PRIORITY_CHANGE = 500;
public static final int ANIMATION_DELAY_PER_ELEMENT_INTERRUPTING = 80;
public static final int ANIMATION_DELAY_PER_ELEMENT_MANUAL = 32;
public static final int ANIMATION_DELAY_PER_ELEMENT_GO_TO_FULL_SHADE = 48;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
index 662c744..46c873d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java
@@ -93,7 +93,6 @@
private boolean mIsVertical;
private boolean mAlternativeOrder;
- private boolean mUsingCustomLayout;
private OverviewProxyService mOverviewProxyService;
private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
@@ -145,7 +144,6 @@
@Override
public void onNavigationModeChanged(int mode) {
mNavBarMode = mode;
- onLikelyDefaultLayoutChange();
}
@Override
@@ -154,17 +152,7 @@
super.onDetachedFromWindow();
}
- public void setNavigationBarLayout(String layoutValue) {
- if (!Objects.equals(mCurrentLayout, layoutValue)) {
- mUsingCustomLayout = layoutValue != null;
- clearViews();
- inflateLayout(layoutValue);
- }
- }
-
public void onLikelyDefaultLayoutChange() {
- // Don't override custom layouts
- if (mUsingCustomLayout) return;
// Reevaluate new layout
final String newValue = getDefaultLayout();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index 06b7d1a..daefef5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -16,19 +16,14 @@
package com.android.systemui.statusbar.phone;
-import static android.content.Intent.ACTION_OVERLAY_CHANGED;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
-import android.content.BroadcastReceiver;
import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
import android.content.om.IOverlayManager;
import android.content.om.OverlayInfo;
import android.content.pm.PackageManager;
import android.content.res.ApkAssets;
-import android.os.PatternMatcher;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -39,6 +34,7 @@
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import java.io.FileDescriptor;
@@ -69,16 +65,6 @@
private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
- private BroadcastReceiver mReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (DEBUG) {
- Log.d(TAG, "ACTION_OVERLAY_CHANGED");
- }
- updateCurrentInteractionMode(true /* notify */);
- }
- };
-
private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback =
new DeviceProvisionedController.DeviceProvisionedListener() {
@Override
@@ -97,6 +83,7 @@
@Inject
public NavigationModeController(Context context,
DeviceProvisionedController deviceProvisionedController,
+ ConfigurationController configurationController,
@UiBackground Executor uiBgExecutor) {
mContext = context;
mCurrentUserContext = context;
@@ -105,10 +92,15 @@
mUiBgExecutor = uiBgExecutor;
deviceProvisionedController.addCallback(mDeviceProvisionedCallback);
- IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
- overlayFilter.addDataScheme("package");
- overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
- mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
+ configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onOverlayChanged() {
+ if (DEBUG) {
+ Log.d(TAG, "onOverlayChanged");
+ }
+ updateCurrentInteractionMode(true /* notify */);
+ }
+ });
updateCurrentInteractionMode(false /* notify */);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 9b377ca..c89f6c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -319,7 +319,7 @@
verify(mNotificationEntryManager).updateNotifications(any());
mBubbleController.removeBubble(
- mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
@@ -331,7 +331,7 @@
mBubbleController.updateBubble(mRow2.getEntry());
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.removeBubble(
- mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
Bubble b = mBubbleData.getOverflowBubbleWithKey(mRow.getEntry().getKey());
assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(b));
@@ -352,9 +352,10 @@
mBubbleController.updateBubble(mRow.getEntry(), /* suppressFlyout */
false, /* showInShade */ true);
mBubbleController.removeBubble(
- mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
- mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL);
+ mBubbleController.removeBubble(
+ mRow.getEntry().getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
verify(mNotificationEntryManager, times(1)).performRemoveNotification(
eq(mRow.getEntry().getSbn()), anyInt());
assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
@@ -367,7 +368,7 @@
assertTrue(mBubbleController.hasBubbles());
mBubbleController.removeBubble(
- mRow.getEntry(), BubbleController.DISMISS_USER_CHANGED);
+ mRow.getEntry().getKey(), BubbleController.DISMISS_USER_CHANGED);
verify(mNotificationEntryManager, never()).performRemoveNotification(
eq(mRow.getEntry().getSbn()), anyInt());
assertFalse(mBubbleController.hasBubbles());
@@ -565,7 +566,8 @@
// Dismiss currently expanded
mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
+ mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey())
+ .getEntry().getKey(),
BubbleController.DISMISS_USER_GESTURE);
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
@@ -576,7 +578,8 @@
// Dismiss that one
mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
+ mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey())
+ .getEntry().getKey(),
BubbleController.DISMISS_USER_GESTURE);
// Make sure state changes and collapse happens
@@ -702,7 +705,7 @@
@Test
public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry());
- mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED);
+ mBubbleController.removeBubble(mRow.getEntry().getKey(), BubbleController.DISMISS_AGED);
verify(mDeleteIntent, never()).send();
}
@@ -710,7 +713,7 @@
public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.removeBubble(
- mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
verify(mDeleteIntent, times(1)).send();
}
@@ -813,7 +816,7 @@
// Dismiss the bubble into overflow.
mBubbleController.removeBubble(
- mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
assertFalse(mBubbleController.hasBubbles());
boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
@@ -834,7 +837,7 @@
mRow.getEntry()));
mBubbleController.removeBubble(
- mRow.getEntry(), BubbleController.DISMISS_NO_LONGER_BUBBLE);
+ mRow.getEntry().getKey(), BubbleController.DISMISS_NO_LONGER_BUBBLE);
assertFalse(mBubbleController.hasBubbles());
boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
@@ -856,12 +859,12 @@
mBubbleData.setMaxOverflowBubbles(1);
mBubbleController.removeBubble(
- mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
assertEquals(mBubbleData.getBubbles().size(), 2);
assertEquals(mBubbleData.getOverflowBubbles().size(), 1);
mBubbleController.removeBubble(
- mRow2.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ mRow2.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
// Overflow max of 1 is reached; mRow is oldest, so it gets removed
verify(mNotificationEntryManager, times(1)).performRemoveNotification(
mRow.getEntry().getSbn(), REASON_CANCEL);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index eca78ec..8224c88e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -177,7 +177,8 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(
+ mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
// Verify
verifyUpdateReceived();
@@ -299,12 +300,14 @@
mBubbleData.setListener(mListener);
mBubbleData.setMaxOverflowBubbles(1);
- mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(
+ mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertOverflowChangedTo(ImmutableList.of(mBubbleA1));
// Overflow max of 1 is reached; A1 is oldest, so it gets removed
- mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(
+ mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
}
@@ -325,12 +328,14 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_NOTIF_CANCEL);
+ mBubbleData.notificationEntryRemoved(mEntryA1.getKey(),
+ BubbleController.DISMISS_NOTIF_CANCEL);
verifyUpdateReceived();
assertOverflowChangedTo(ImmutableList.of(mBubbleA2));
// Test
- mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_GROUP_CANCELLED);
+ mBubbleData.notificationEntryRemoved(mEntryA2.getKey(),
+ BubbleController.DISMISS_GROUP_CANCELLED);
verifyUpdateReceived();
assertOverflowChangedTo(ImmutableList.of());
}
@@ -410,7 +415,8 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(
+ mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
// TODO: this should fail if things work as I expect them to?
assertOrderChangedTo(mBubbleB2, mBubbleB1, mBubbleA1);
@@ -430,7 +436,8 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(
+ mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertOrderNotChanged();
}
@@ -449,7 +456,8 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_NOTIF_CANCEL);
+ mBubbleData.notificationEntryRemoved(
+ mEntryA2.getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
verifyUpdateReceived();
assertSelectionChangedTo(mBubbleB2);
}
@@ -523,7 +531,8 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(
+ mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
// Verify the selection was cleared.
verifyUpdateReceived();
@@ -623,7 +632,8 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(mEntryB2, BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(
+ mEntryB2.getKey(), BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertOrderChangedTo(mBubbleA2, mBubbleB1, mBubbleA1);
}
@@ -647,11 +657,13 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(mEntryA2, BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(
+ mEntryA2.getKey(), BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertSelectionChangedTo(mBubbleB1);
- mBubbleData.notificationEntryRemoved(mEntryB1, BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(
+ mEntryB1.getKey(), BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertSelectionChangedTo(mBubbleA1);
}
@@ -765,7 +777,8 @@
mBubbleData.setListener(mListener);
// Test
- mBubbleData.notificationEntryRemoved(mEntryA1, BubbleController.DISMISS_USER_GESTURE);
+ mBubbleData.notificationEntryRemoved(
+ mEntryA1.getKey(), BubbleController.DISMISS_USER_GESTURE);
verifyUpdateReceived();
assertExpandedChangedTo(false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
index b18d67b..ead95ca1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java
@@ -287,7 +287,8 @@
assertTrue(mBubbleController.hasBubbles());
verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
- mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleController.removeBubble(
+ mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()));
verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
}
@@ -304,7 +305,8 @@
mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).setSuppressNotification(true);
// Now remove the bubble
- mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ mBubbleController.removeBubble(
+ mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey()));
// We don't remove the notification since the bubble is still in overflow.
@@ -324,7 +326,8 @@
mBubbleData.getBubbleInStackWithKey(mRow.getEntry().getKey()).setSuppressNotification(true);
// Now remove the bubble
- mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL);
+ mBubbleController.removeBubble(
+ mRow.getEntry().getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getEntry().getKey()));
// Since the notif is dismissed and not in overflow, once the bubble is removed,
@@ -504,7 +507,8 @@
// Dismiss currently expanded
mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
+ mBubbleData.getBubbleInStackWithKey(
+ stackView.getExpandedBubble().getKey()).getEntry().getKey(),
BubbleController.DISMISS_USER_GESTURE);
verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getEntry().getKey());
@@ -515,7 +519,8 @@
// Dismiss that one
mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(stackView.getExpandedBubble().getKey()).getEntry(),
+ mBubbleData.getBubbleInStackWithKey(
+ stackView.getExpandedBubble().getKey()).getEntry().getKey(),
BubbleController.DISMISS_USER_GESTURE);
// Make sure state changes and collapse happens
@@ -615,7 +620,7 @@
@Test
public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry());
- mBubbleController.removeBubble(mRow.getEntry(), BubbleController.DISMISS_AGED);
+ mBubbleController.removeBubble(mRow.getEntry().getKey(), BubbleController.DISMISS_AGED);
verify(mDeleteIntent, never()).send();
}
@@ -623,7 +628,7 @@
public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
mBubbleController.updateBubble(mRow.getEntry());
mBubbleController.removeBubble(
- mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
verify(mDeleteIntent, times(1)).send();
}
@@ -691,7 +696,7 @@
// Dismiss the bubble
mBubbleController.removeBubble(
- mRow.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+ mRow.getEntry().getKey(), BubbleController.DISMISS_USER_GESTURE);
assertFalse(mBubbleController.hasBubbles());
// Dismiss the notification
@@ -712,7 +717,7 @@
// Dismiss the bubble
mBubbleController.removeBubble(
- mRow.getEntry(), BubbleController.DISMISS_NOTIF_CANCEL);
+ mRow.getEntry().getKey(), BubbleController.DISMISS_NOTIF_CANCEL);
assertFalse(mBubbleController.hasBubbles());
// Dismiss the notification
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt
index d49d021..f468192 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt
@@ -29,9 +29,9 @@
class BubblePersistentRepositoryTest : SysuiTestCase() {
private val bubbles = listOf(
- BubbleEntity(0, "com.example.messenger", "shortcut-1"),
- BubbleEntity(10, "com.example.chat", "alice and bob"),
- BubbleEntity(0, "com.example.messenger", "shortcut-2")
+ BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1"),
+ BubbleEntity(10, "com.example.chat", "alice and bob", "key-2"),
+ BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3")
)
private lateinit var repository: BubblePersistentRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt
index 7acc937..ee48846 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt
@@ -28,9 +28,9 @@
@RunWith(AndroidTestingRunner::class)
class BubbleVolatileRepositoryTest : SysuiTestCase() {
- private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1")
- private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob")
- private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2")
+ private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1")
+ private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob", "k2")
+ private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3")
private val bubbles = listOf(bubble1, bubble2, bubble3)
private lateinit var repository: BubbleVolatileRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt
index ef4580c..79701ec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt
@@ -31,17 +31,17 @@
class BubbleXmlHelperTest : SysuiTestCase() {
private val bubbles = listOf(
- BubbleEntity(0, "com.example.messenger", "shortcut-1"),
- BubbleEntity(10, "com.example.chat", "alice and bob"),
- BubbleEntity(0, "com.example.messenger", "shortcut-2")
+ BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1"),
+ BubbleEntity(10, "com.example.chat", "alice and bob", "k2"),
+ BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3")
)
@Test
fun testWriteXml() {
val expectedEntries = """
- <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" />
- <bb uid="10" pkg="com.example.chat" sid="alice and bob" />
- <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" />
+ <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" />
+ <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" />
+ <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" />
""".trimIndent()
ByteArrayOutputStream().use {
writeXml(it, bubbles)
@@ -56,9 +56,9 @@
val src = """
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<bs>
- <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" />
- <bb uid="10" pkg="com.example.chat" sid="alice and bob" />
- <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" />
+ <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" />
+ <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" />
+ <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" />
</bs>
""".trimIndent()
val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
new file mode 100644
index 0000000..7fe6827
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.dagger
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.ui.ControlsUiController
+import dagger.Lazy
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsComponentTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var controller: ControlsController
+ @Mock
+ private lateinit var uiController: ControlsUiController
+ @Mock
+ private lateinit var listingController: ControlsListingController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun testFeatureEnabled() {
+ val component = ControlsComponent(
+ true,
+ Lazy { controller },
+ Lazy { uiController },
+ Lazy { listingController }
+ )
+
+ assertTrue(component.getControlsController().isPresent)
+ assertEquals(controller, component.getControlsController().get())
+ assertTrue(component.getControlsUiController().isPresent)
+ assertEquals(uiController, component.getControlsUiController().get())
+ assertTrue(component.getControlsListingController().isPresent)
+ assertEquals(listingController, component.getControlsListingController().get())
+ }
+
+ @Test
+ fun testFeatureDisabled() {
+ val component = ControlsComponent(
+ false,
+ Lazy { controller },
+ Lazy { uiController },
+ Lazy { listingController }
+ )
+
+ assertFalse(component.getControlsController().isPresent)
+ assertFalse(component.getControlsUiController().isPresent)
+ assertFalse(component.getControlsListingController().isPresent)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
index 663f011..ee1cc7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt
@@ -66,6 +66,7 @@
MockitoAnnotations.initMocks(this)
mContext.setMockPackageManager(packageManager)
+ `when`(packageManager.hasSystemFeature(PackageManager.FEATURE_CONTROLS)).thenReturn(true)
mContext.addMockSystemService(ActivityManager::class.java, activityManager)
receiver = ControlsRequestReceiver()
@@ -145,6 +146,14 @@
} ?: run { fail("Null start intent") }
}
+ @Test
+ fun testFeatureDisabled_activityNotStarted() {
+ `when`(packageManager.hasSystemFeature(PackageManager.FEATURE_CONTROLS)).thenReturn(false)
+ receiver.onReceive(wrapper, intent)
+
+ assertNull(wrapper.intent)
+ }
+
class MyWrapper(context: Context) : ContextWrapper(context) {
var intent: Intent? = null
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
index 487452b..3254633 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogTest.java
@@ -59,6 +59,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.controls.controller.ControlsController;
+import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsUiController;
import com.android.systemui.model.SysUiState;
@@ -121,6 +122,7 @@
@Mock GlobalActionsPanelPlugin.PanelViewController mWalletController;
@Mock private Handler mHandler;
@Mock private CurrentUserContextTracker mCurrentUserContextTracker;
+ private ControlsComponent mControlsComponent;
private TestableLooper mTestableLooper;
@@ -132,6 +134,13 @@
when(mRingerModeTracker.getRingerMode()).thenReturn(mRingerModeLiveData);
when(mCurrentUserContextTracker.getCurrentUserContext()).thenReturn(mContext);
+ mControlsComponent = new ControlsComponent(
+ true,
+ () -> mControlsController,
+ () -> mControlsUiController,
+ () -> mControlsListingController
+ );
+
mGlobalActionsDialog = new GlobalActionsDialog(mContext,
mWindowManagerFuncs,
mAudioManager,
@@ -156,15 +165,13 @@
mColorExtractor,
mStatusBarService,
mNotificationShadeWindowController,
- mControlsUiController,
mWindowManager,
mBackgroundExecutor,
- mControlsListingController,
- mControlsController,
mUiEventLogger,
mRingerModeTracker,
mSysUiState,
mHandler,
+ mControlsComponent,
mCurrentUserContextTracker
);
mGlobalActionsDialog.setZeroDialogPressDelayForTesting();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
index 4b21ef2..0272028 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -60,6 +60,7 @@
import android.content.pm.ShortcutManager;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
+import android.os.Handler;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
@@ -72,7 +73,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.settingslib.notification.ConversationIconFactory;
-import com.android.systemui.Dependency;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
@@ -153,13 +153,14 @@
private Provider<PriorityOnboardingDialogController.Builder> mBuilderProvider = () -> mBuilder;
@Mock
private Notification.BubbleMetadata mBubbleMetadata;
+ private Handler mTestHandler;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mTestableLooper = TestableLooper.get(this);
- mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+ mTestHandler = new Handler(mTestableLooper.getLooper());
mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
mDependency.injectTestDependency(BubbleController.class, mBubbleController);
mDependency.injectTestDependency(ShadeController.class, mShadeController);
@@ -253,7 +254,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
assertEquals(mIconDrawable, view.getDrawable());
}
@@ -275,7 +278,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
assertTrue(textView.getText().toString().contains("App Name"));
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -324,7 +329,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
assertTrue(textView.getText().toString().contains(group.getName()));
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
@@ -348,7 +355,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
assertEquals(GONE, textView.getVisibility());
@@ -371,7 +380,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(GONE, nameView.getVisibility());
}
@@ -404,7 +415,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
assertEquals(VISIBLE, nameView.getVisibility());
assertTrue(nameView.getText().toString().contains("Proxied"));
@@ -430,7 +443,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
settingsButton.performClick();
@@ -454,7 +469,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -479,7 +496,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- false);
+ false,
+ mTestHandler,
+ mTestHandler);
final View settingsButton = mNotificationInfo.findViewById(R.id.info);
assertTrue(settingsButton.getVisibility() != View.VISIBLE);
}
@@ -502,7 +521,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
View view = mNotificationInfo.findViewById(R.id.silence);
assertThat(view.isSelected()).isTrue();
}
@@ -528,7 +549,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
View view = mNotificationInfo.findViewById(R.id.default_behavior);
assertThat(view.isSelected()).isTrue();
assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo(
@@ -557,7 +580,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
View view = mNotificationInfo.findViewById(R.id.default_behavior);
assertThat(view.isSelected()).isTrue();
assertThat(((TextView) view.findViewById(R.id.default_summary)).getText()).isEqualTo(
@@ -585,7 +610,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
View fave = mNotificationInfo.findViewById(R.id.priority);
fave.performClick();
@@ -627,7 +654,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
mNotificationInfo.findViewById(R.id.default_behavior).performClick();
mTestableLooper.processAllMessages();
@@ -668,7 +697,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
View silence = mNotificationInfo.findViewById(R.id.silence);
@@ -710,7 +741,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
View fave = mNotificationInfo.findViewById(R.id.priority);
fave.performClick();
@@ -745,7 +778,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
View fave = mNotificationInfo.findViewById(R.id.priority);
fave.performClick();
@@ -778,7 +813,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
mNotificationInfo.findViewById(R.id.default_behavior).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -812,7 +849,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
mNotificationInfo.findViewById(R.id.default_behavior).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -846,7 +885,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
mNotificationInfo.findViewById(R.id.default_behavior).performClick();
mNotificationInfo.findViewById(R.id.done).performClick();
@@ -879,7 +920,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
View silence = mNotificationInfo.findViewById(R.id.silence);
silence.performClick();
@@ -911,7 +954,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
@@ -934,7 +979,9 @@
mIconFactory,
mContext,
mBuilderProvider,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID));
@@ -967,7 +1014,9 @@
mIconFactory,
mContext,
() -> b,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
// WHEN user clicks "priority"
mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
@@ -1001,7 +1050,9 @@
mIconFactory,
mContext,
() -> b,
- true);
+ true,
+ mTestHandler,
+ mTestHandler);
// WHEN user clicks "priority"
mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index eeb912e..da7d249 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -144,7 +144,7 @@
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager,
- () -> mStatusBar, mHandler, mAccessibilityManager, mHighPriorityProvider,
+ () -> mStatusBar, mHandler, mHandler, mAccessibilityManager, mHighPriorityProvider,
mINotificationManager, mLauncherApps, mShortcutManager,
mChannelEditorDialogController, mContextTracker, mProvider);
mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 103151d..26e85be 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -51,7 +51,7 @@
*/
public class AppPredictionPerUserService extends
AbstractPerUserSystemService<AppPredictionPerUserService, AppPredictionManagerService>
- implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks {
+ implements RemoteAppPredictionService.RemoteAppPredictionServiceCallbacks {
private static final String TAG = AppPredictionPerUserService.class.getSimpleName();
private static final String PREDICT_USING_PEOPLE_SERVICE_PREFIX =
@@ -114,8 +114,11 @@
public void onCreatePredictionSessionLocked(@NonNull AppPredictionContext context,
@NonNull AppPredictionSessionId sessionId) {
if (!mSessionInfos.containsKey(sessionId)) {
+ // TODO(b/157500121): remove below forceUsingPeopleService logic after testing
+ // PeopleService for 2 weeks on Droidfood.
+ final boolean forceUsingPeopleService = context.getUiSurface().equals("share");
mSessionInfos.put(sessionId, new AppPredictionSessionInfo(sessionId, context,
- DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
+ forceUsingPeopleService || DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI,
PREDICT_USING_PEOPLE_SERVICE_PREFIX + context.getUiSurface(), false),
this::removeAppPredictionSessionInfo));
}
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index f3c7874..a33817c 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -65,6 +65,7 @@
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.NetworkProvider;
+import android.net.NetworkRequest;
import android.net.RouteInfo;
import android.net.UidRange;
import android.net.VpnManager;
@@ -2225,12 +2226,27 @@
@Override
public void run() {
- // Explicitly use only the network that ConnectivityService thinks is the "best." In
- // other words, only ever use the currently selected default network. This does mean
- // that in both onLost() and onConnected(), any old sessions MUST be torn down. This
- // does NOT include VPNs.
+ // Unless the profile is restricted to test networks, explicitly use only the network
+ // that ConnectivityService thinks is the "best." In other words, only ever use the
+ // currently selected default network. This does mean that in both onLost() and
+ // onConnected(), any old sessions MUST be torn down. This does NOT include VPNs.
+ //
+ // When restricted to test networks, select any network with TRANSPORT_TEST. Since the
+ // creator of the profile and the test network creator both have MANAGE_TEST_NETWORKS,
+ // this is considered safe.
final ConnectivityManager cm = ConnectivityManager.from(mContext);
- cm.requestNetwork(cm.getDefaultRequest(), mNetworkCallback);
+ final NetworkRequest req;
+
+ if (mProfile.isRestrictedToTestNetworks()) {
+ req = new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+ .build();
+ } else {
+ req = cm.getDefaultRequest();
+ }
+
+ cm.requestNetwork(req, mNetworkCallback);
}
private boolean isActiveNetwork(@Nullable Network network) {
@@ -2868,6 +2884,11 @@
verifyCallingUidAndPackage(packageName);
enforceNotRestrictedUser();
+ if (profile.isRestrictedToTestNetworks) {
+ mContext.enforceCallingPermission(Manifest.permission.MANAGE_TEST_NETWORKS,
+ "Test-mode profiles require the MANAGE_TEST_NETWORKS permission");
+ }
+
final byte[] encodedProfile = profile.encode();
if (encodedProfile.length > MAX_VPN_PROFILE_SIZE_BYTES) {
throw new IllegalArgumentException("Profile too big");
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
index 0fa522c..ee02e3f 100644
--- a/services/core/java/com/android/server/notification/ShortcutHelper.java
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -208,7 +208,7 @@
if (shortcutInfo.isLongLived() && !shortcutInfo.isCached()) {
mShortcutServiceInternal.cacheShortcuts(user.getIdentifier(), "android",
shortcutInfo.getPackage(), Collections.singletonList(shortcutInfo.getId()),
- shortcutInfo.getUserId());
+ shortcutInfo.getUserId(), ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
}
}
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index c6d08c3..5bbe490 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -18,6 +18,8 @@
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.content.pm.LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
+import static android.content.pm.LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -78,6 +80,7 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
+import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -780,26 +783,28 @@
@Override
public void cacheShortcuts(String callingPackage, String packageName, List<String> ids,
- UserHandle targetUser) {
+ UserHandle targetUser, int cacheFlags) {
ensureStrictAccessShortcutsPermission(callingPackage);
if (!canAccessProfile(targetUser.getIdentifier(), "Cannot cache shortcuts")) {
return;
}
- mShortcutServiceInternal.cacheShortcuts(getCallingUserId(),
- callingPackage, packageName, ids, targetUser.getIdentifier());
+ mShortcutServiceInternal.cacheShortcuts(
+ getCallingUserId(), callingPackage, packageName, ids,
+ targetUser.getIdentifier(), toShortcutsCacheFlags(cacheFlags));
}
@Override
public void uncacheShortcuts(String callingPackage, String packageName, List<String> ids,
- UserHandle targetUser) {
+ UserHandle targetUser, int cacheFlags) {
ensureStrictAccessShortcutsPermission(callingPackage);
if (!canAccessProfile(targetUser.getIdentifier(), "Cannot uncache shortcuts")) {
return;
}
- mShortcutServiceInternal.uncacheShortcuts(getCallingUserId(),
- callingPackage, packageName, ids, targetUser.getIdentifier());
+ mShortcutServiceInternal.uncacheShortcuts(
+ getCallingUserId(), callingPackage, packageName, ids,
+ targetUser.getIdentifier(), toShortcutsCacheFlags(cacheFlags));
}
@Override
@@ -1058,6 +1063,18 @@
user.getIdentifier(), debugMsg, false);
}
+ private int toShortcutsCacheFlags(int cacheFlags) {
+ int ret = 0;
+ if (cacheFlags == FLAG_CACHE_NOTIFICATION_SHORTCUTS) {
+ ret = ShortcutInfo.FLAG_CACHED_NOTIFICATIONS;
+ } else if (cacheFlags == FLAG_CACHE_BUBBLE_SHORTCUTS) {
+ ret = ShortcutInfo.FLAG_CACHED_BUBBLES;
+ }
+ Preconditions.checkArgumentPositive(ret, "Invalid cache owner");
+
+ return ret;
+ }
+
@VisibleForTesting
void postToPackageMonitorHandler(Runnable r) {
mCallbackHandler.post(r);
@@ -1154,7 +1171,7 @@
final int shortcutFlags = (matchDynamic ? ShortcutInfo.FLAG_DYNAMIC : 0)
| (matchPinned ? ShortcutInfo.FLAG_PINNED : 0)
| (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
- | (matchCached ? ShortcutInfo.FLAG_CACHED : 0);
+ | (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0);
for (int i = 0; i < shortcuts.size(); i++) {
final ShortcutInfo si = shortcuts.get(i);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 1642607..9e27f65 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -287,7 +287,7 @@
if (shortcut != null) {
mShortcutUser.mService.removeIconLocked(shortcut);
shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
- | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED);
+ | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL);
}
return shortcut;
}
@@ -323,36 +323,18 @@
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
-
- final boolean replaced;
-
- final boolean wasPinned;
- final boolean wasCached;
-
- if (oldShortcut == null) {
- replaced = false;
- wasPinned = false;
- wasCached = false;
- } else {
+ if (oldShortcut != null) {
// It's an update case.
// Make sure the target is updatable. (i.e. should be mutable.)
oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false);
- replaced = true;
- wasPinned = oldShortcut.isPinned();
- wasCached = oldShortcut.isCached();
- }
-
- // If it was originally pinned, the new one should be pinned too.
- if (wasPinned) {
- newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
- }
- if (wasCached) {
- newShortcut.addFlags(ShortcutInfo.FLAG_CACHED);
+ // If it was originally pinned or cached, the new one should be pinned or cached too.
+ newShortcut.addFlags(oldShortcut.getFlags()
+ & (ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_CACHED_ALL));
}
forceReplaceShortcutInner(newShortcut);
- return replaced;
+ return oldShortcut != null;
}
/**
@@ -373,9 +355,6 @@
changedShortcuts.clear();
final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
- boolean wasPinned = false;
- boolean wasCached = false;
-
boolean deleted = false;
if (oldShortcut == null) {
@@ -408,16 +387,9 @@
// Make sure the target is updatable. (i.e. should be mutable.)
oldShortcut.ensureUpdatableWith(newShortcut, /*isUpdating=*/ false);
- wasPinned = oldShortcut.isPinned();
- wasCached = oldShortcut.isCached();
- }
-
- // If it was originally pinned or cached, the new one should be pinned or cached too.
- if (wasPinned) {
- newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
- }
- if (wasCached) {
- newShortcut.addFlags(ShortcutInfo.FLAG_CACHED);
+ // If it was originally pinned or cached, the new one should be pinned or cached too.
+ newShortcut.addFlags(oldShortcut.getFlags()
+ & (ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_CACHED_ALL));
}
forceReplaceShortcutInner(newShortcut);
@@ -511,7 +483,7 @@
public ShortcutInfo deleteLongLivedWithId(@NonNull String shortcutId, boolean ignoreInvisible) {
final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
if (shortcut != null) {
- shortcut.clearFlags(ShortcutInfo.FLAG_CACHED);
+ shortcut.clearFlags(ShortcutInfo.FLAG_CACHED_ALL);
}
return deleteOrDisableWithId(
shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false, ignoreInvisible,
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 3732b47..3ec1397 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2407,7 +2407,7 @@
final int shortcutFlags = (matchDynamic ? ShortcutInfo.FLAG_DYNAMIC : 0)
| (matchPinned ? ShortcutInfo.FLAG_PINNED : 0)
| (matchManifest ? ShortcutInfo.FLAG_MANIFEST : 0)
- | (matchCached ? ShortcutInfo.FLAG_CACHED : 0);
+ | (matchCached ? ShortcutInfo.FLAG_CACHED_ALL : 0);
return getShortcutsWithQueryLocked(
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
@@ -3045,17 +3045,17 @@
@Override
public void cacheShortcuts(int launcherUserId,
@NonNull String callingPackage, @NonNull String packageName,
- @NonNull List<String> shortcutIds, int userId) {
+ @NonNull List<String> shortcutIds, int userId, int cacheFlags) {
updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds,
- userId, /* doCache= */ true);
+ userId, cacheFlags, /* doCache= */ true);
}
@Override
public void uncacheShortcuts(int launcherUserId,
@NonNull String callingPackage, @NonNull String packageName,
- @NonNull List<String> shortcutIds, int userId) {
+ @NonNull List<String> shortcutIds, int userId, int cacheFlags) {
updateCachedShortcutsInternal(launcherUserId, callingPackage, packageName, shortcutIds,
- userId, /* doCache= */ false);
+ userId, cacheFlags, /* doCache= */ false);
}
@Override
@@ -3079,10 +3079,12 @@
private void updateCachedShortcutsInternal(int launcherUserId,
@NonNull String callingPackage, @NonNull String packageName,
- @NonNull List<String> shortcutIds, int userId, boolean doCache) {
+ @NonNull List<String> shortcutIds, int userId, int cacheFlags, boolean doCache) {
// Calling permission must be checked by LauncherAppsImpl.
Preconditions.checkStringNotEmpty(packageName, "packageName");
Objects.requireNonNull(shortcutIds, "shortcutIds");
+ Preconditions.checkState(
+ (cacheFlags & ShortcutInfo.FLAG_CACHED_ALL) != 0, "invalid cacheFlags");
List<ShortcutInfo> changedShortcuts = null;
List<ShortcutInfo> removedShortcuts = null;
@@ -3101,13 +3103,13 @@
for (int i = 0; i < idSize; i++) {
final String id = Preconditions.checkStringNotEmpty(shortcutIds.get(i));
final ShortcutInfo si = sp.findShortcutById(id);
- if (si == null || doCache == si.isCached()) {
+ if (si == null || doCache == si.hasFlags(cacheFlags)) {
continue;
}
if (doCache) {
if (si.isLongLived()) {
- si.addFlags(ShortcutInfo.FLAG_CACHED);
+ si.addFlags(cacheFlags);
if (changedShortcuts == null) {
changedShortcuts = new ArrayList<>(1);
}
@@ -3118,9 +3120,8 @@
}
} else {
ShortcutInfo removed = null;
- if (si.isDynamic()) {
- si.clearFlags(ShortcutInfo.FLAG_CACHED);
- } else {
+ si.clearFlags(cacheFlags);
+ if (!si.isDynamic() && !si.isCached()) {
removed = sp.deleteLongLivedWithId(id, /*ignoreInvisible=*/ true);
}
if (removed != null) {
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 29c1243..0b3254f 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -179,6 +179,7 @@
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
/**
* SystemService containing PullAtomCallbacks that are registered with statsd.
@@ -325,6 +326,7 @@
case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG:
+ case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED:
return pullDataBytesTransfer(atomTag, data);
case FrameworkStatsLog.BLUETOOTH_BYTES_TRANSFER:
return pullBluetoothBytesTransfer(atomTag, data);
@@ -641,11 +643,14 @@
collectNetworkStatsSnapshotForAtom(FrameworkStatsLog.MOBILE_BYTES_TRANSFER));
mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG));
+ mNetworkStatsBaselines.addAll(collectNetworkStatsSnapshotForAtom(
+ FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED));
registerWifiBytesTransfer();
registerWifiBytesTransferBackground();
registerMobileBytesTransfer();
registerMobileBytesTransferBackground();
+ registerBytesTransferByTagAndMetered();
}
/**
@@ -787,50 +792,94 @@
private static class NetworkStatsExt {
@NonNull
public final NetworkStats stats;
- public final int transport;
- public final boolean withFgbg;
+ public final int[] transports;
+ public final boolean slicedByFgbg;
+ public final boolean slicedByTag;
+ public final boolean slicedByMetered;
- NetworkStatsExt(@NonNull NetworkStats stats, int transport, boolean withFgbg) {
+ NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg) {
+ this(stats, transports, slicedByFgbg, /*slicedByTag=*/false, /*slicedByMetered=*/false);
+ }
+
+ NetworkStatsExt(@NonNull NetworkStats stats, int[] transports, boolean slicedByFgbg,
+ boolean slicedByTag, boolean slicedByMetered) {
this.stats = stats;
- this.transport = transport;
- this.withFgbg = withFgbg;
+
+ // Sort transports array so that we can test for equality without considering order.
+ this.transports = Arrays.copyOf(transports, transports.length);
+ Arrays.sort(this.transports);
+
+ this.slicedByFgbg = slicedByFgbg;
+ this.slicedByTag = slicedByTag;
+ this.slicedByMetered = slicedByMetered;
+ }
+
+ public boolean hasSameSlicing(@NonNull NetworkStatsExt other) {
+ return Arrays.equals(transports, other.transports) && slicedByFgbg == other.slicedByFgbg
+ && slicedByTag == other.slicedByTag && slicedByMetered == other.slicedByMetered;
}
}
@NonNull
private List<NetworkStatsExt> collectNetworkStatsSnapshotForAtom(int atomTag) {
+ List<NetworkStatsExt> ret = new ArrayList<>();
switch(atomTag) {
- case FrameworkStatsLog.WIFI_BYTES_TRANSFER:
- return collectUidNetworkStatsSnapshot(TRANSPORT_WIFI, /*withFgbg=*/false);
- case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG:
- return collectUidNetworkStatsSnapshot(TRANSPORT_WIFI, /*withFgbg=*/true);
- case FrameworkStatsLog.MOBILE_BYTES_TRANSFER:
- return collectUidNetworkStatsSnapshot(TRANSPORT_CELLULAR, /*withFgbg=*/false);
- case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG:
- return collectUidNetworkStatsSnapshot(TRANSPORT_CELLULAR, /*withFgbg=*/true);
+ case FrameworkStatsLog.WIFI_BYTES_TRANSFER: {
+ final NetworkStats stats = getUidNetworkStatsSnapshot(TRANSPORT_WIFI,
+ /*includeTags=*/false);
+ if (stats != null) {
+ ret.add(new NetworkStatsExt(stats.groupedByUid(), new int[] {TRANSPORT_WIFI},
+ /*slicedByFgbg=*/false));
+ }
+ break;
+ }
+ case FrameworkStatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
+ final NetworkStats stats = getUidNetworkStatsSnapshot(TRANSPORT_WIFI,
+ /*includeTags=*/false);
+ if (stats != null) {
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
+ new int[] {TRANSPORT_WIFI}, /*slicedByFgbg=*/true));
+ }
+ break;
+ }
+ case FrameworkStatsLog.MOBILE_BYTES_TRANSFER: {
+ final NetworkStats stats = getUidNetworkStatsSnapshot(TRANSPORT_CELLULAR,
+ /*includeTags=*/false);
+ if (stats != null) {
+ ret.add(new NetworkStatsExt(stats.groupedByUid(),
+ new int[] {TRANSPORT_CELLULAR}, /*slicedByFgbg=*/false));
+ }
+ break;
+ }
+ case FrameworkStatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
+ final NetworkStats stats = getUidNetworkStatsSnapshot(TRANSPORT_CELLULAR,
+ /*includeTags=*/false);
+ if (stats != null) {
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidAndFgbg(stats),
+ new int[] {TRANSPORT_CELLULAR}, /*slicedByFgbg=*/true));
+ }
+ break;
+ }
+ case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED: {
+ final NetworkStats wifiStats = getUidNetworkStatsSnapshot(TRANSPORT_WIFI,
+ /*includeTags=*/true);
+ final NetworkStats cellularStats = getUidNetworkStatsSnapshot(TRANSPORT_CELLULAR,
+ /*includeTags=*/true);
+ if (wifiStats != null && cellularStats != null) {
+ final NetworkStats stats = wifiStats.add(cellularStats);
+ ret.add(new NetworkStatsExt(sliceNetworkStatsByUidTagAndMetered(stats),
+ new int[] {TRANSPORT_WIFI, TRANSPORT_CELLULAR},
+ /*slicedByFgbg=*/false, /*slicedByTag=*/true,
+ /*slicedByMetered=*/true));
+ }
+ break;
+ }
default:
throw new IllegalArgumentException("Unknown atomTag " + atomTag);
}
- }
-
- // Get a snapshot of Uid NetworkStats. The snapshot contains NetworkStats with its associated
- // information, and wrapped by a list since multiple NetworkStatsExt objects might be collected.
- @NonNull
- private List<NetworkStatsExt> collectUidNetworkStatsSnapshot(int transport, boolean withFgbg) {
- final List<NetworkStatsExt> ret = new ArrayList<>();
- final NetworkTemplate template = (transport == TRANSPORT_CELLULAR
- ? NetworkTemplate.buildTemplateMobileWithRatType(
- /*subscriptionId=*/null, NETWORK_TYPE_ALL)
- : NetworkTemplate.buildTemplateWifiWildcard());
-
- final NetworkStats stats = getUidNetworkStatsSnapshot(template, withFgbg);
- if (stats != null) {
- ret.add(new NetworkStatsExt(stats, transport, withFgbg));
- }
return ret;
}
-
private int pullDataBytesTransfer(
int atomTag, @NonNull List<StatsEvent> pulledData) {
final List<NetworkStatsExt> current = collectNetworkStatsSnapshotForAtom(atomTag);
@@ -842,22 +891,28 @@
for (final NetworkStatsExt item : current) {
final NetworkStatsExt baseline = CollectionUtils.find(mNetworkStatsBaselines,
- it -> it.withFgbg == item.withFgbg && it.transport == item.transport);
+ it -> it.hasSameSlicing(item));
// No matched baseline indicates error has occurred during initialization stage,
// skip reporting anything since the snapshot is invalid.
if (baseline == null) {
- Slog.e(TAG, "baseline is null for " + atomTag + ", transport="
- + item.transport + " , withFgbg=" + item.withFgbg + ", return.");
+ Slog.e(TAG, "baseline is null for " + atomTag + ", return.");
return StatsManager.PULL_SKIP;
}
- final NetworkStatsExt diff = new NetworkStatsExt(item.stats.subtract(
- baseline.stats).removeEmptyEntries(), item.transport, item.withFgbg);
+ final NetworkStatsExt diff = new NetworkStatsExt(
+ item.stats.subtract(baseline.stats).removeEmptyEntries(), item.transports,
+ item.slicedByFgbg, item.slicedByTag, item.slicedByMetered);
// If no diff, skip.
if (diff.stats.size() == 0) continue;
- addNetworkStats(atomTag, pulledData, diff);
+ switch (atomTag) {
+ case FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED:
+ addBytesTransferByTagAndMeteredAtoms(diff, pulledData);
+ break;
+ default:
+ addNetworkStats(atomTag, pulledData, diff);
+ }
}
return StatsManager.PULL_SUCCESS;
}
@@ -879,7 +934,7 @@
}
e.writeInt(entry.uid);
e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
- if (statsExt.withFgbg) {
+ if (statsExt.slicedByFgbg) {
e.writeInt(entry.set);
}
e.writeLong(entry.rxBytes);
@@ -890,14 +945,38 @@
}
}
+ private void addBytesTransferByTagAndMeteredAtoms(@NonNull NetworkStatsExt statsExt,
+ @NonNull List<StatsEvent> pulledData) {
+ final NetworkStats.Entry entry = new NetworkStats.Entry(); // for recycling
+ for (int i = 0; i < statsExt.stats.size(); i++) {
+ statsExt.stats.getValues(i, entry);
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED)
+ .addBooleanAnnotation(ANNOTATION_ID_TRUNCATE_TIMESTAMP, true)
+ .writeInt(entry.uid)
+ .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
+ .writeBoolean(entry.metered == NetworkStats.METERED_YES)
+ .writeInt(entry.tag)
+ .writeLong(entry.rxBytes)
+ .writeLong(entry.rxPackets)
+ .writeLong(entry.txBytes)
+ .writeLong(entry.txPackets)
+ .build();
+ pulledData.add(e);
+ }
+ }
+
/**
* Create a snapshot of NetworkStats since boot, but add 1 bucket duration before boot as a
* buffer to ensure at least one full bucket will be included.
* Note that this should be only used to calculate diff since the snapshot might contains
* some traffic before boot.
*/
- @Nullable private NetworkStats getUidNetworkStatsSnapshot(
- @NonNull NetworkTemplate template, boolean withFgbg) {
+ @Nullable private NetworkStats getUidNetworkStatsSnapshot(int transport, boolean includeTags) {
+ final NetworkTemplate template = (transport == TRANSPORT_CELLULAR)
+ ? NetworkTemplate.buildTemplateMobileWithRatType(
+ /*subscriptionId=*/null, NETWORK_TYPE_ALL)
+ : NetworkTemplate.buildTemplateWifiWildcard();
final long elapsedMillisSinceBoot = SystemClock.elapsedRealtime();
final long currentTimeInMillis = MICROSECONDS.toMillis(SystemClock.currentTimeMicro());
@@ -906,38 +985,72 @@
try {
final NetworkStats stats = getNetworkStatsSession().getSummaryForAllUid(template,
currentTimeInMillis - elapsedMillisSinceBoot - bucketDuration,
- currentTimeInMillis, /*includeTags=*/false);
- return withFgbg ? rollupNetworkStatsByFgbg(stats) : stats.groupedByUid();
+ currentTimeInMillis, includeTags);
+ return stats;
} catch (RemoteException | NullPointerException e) {
- Slog.e(TAG, "Pulling netstats for " + template
- + " fgbg= " + withFgbg + " bytes has error", e);
+ Slog.e(TAG, "Pulling netstats for template=" + template + " and includeTags="
+ + includeTags + " causes error", e);
}
return null;
}
+ @NonNull private NetworkStats sliceNetworkStatsByUidAndFgbg(@NonNull NetworkStats stats) {
+ return sliceNetworkStats(stats,
+ (newEntry, oldEntry) -> {
+ newEntry.uid = oldEntry.uid;
+ newEntry.set = oldEntry.set;
+ });
+ }
+
+ @NonNull private NetworkStats sliceNetworkStatsByUidTagAndMetered(@NonNull NetworkStats stats) {
+ return sliceNetworkStats(stats,
+ (newEntry, oldEntry) -> {
+ newEntry.uid = oldEntry.uid;
+ newEntry.tag = oldEntry.tag;
+ newEntry.metered = oldEntry.metered;
+ });
+ }
+
/**
- * Allows rollups per UID but keeping the set (foreground/background) slicing.
- * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
+ * Slices NetworkStats along the dimensions specified in the slicer lambda and aggregates over
+ * non-sliced dimensions.
+ *
+ * This function iterates through each NetworkStats.Entry, sets its dimensions equal to the
+ * default state (with the presumption that we don't want to slice on anything), and then
+ * applies the slicer lambda to allow users to control which dimensions to slice on. This is
+ * adapted from groupedByUid within NetworkStats.java
+ *
+ * @param slicer An operation taking into two parameters, new NetworkStats.Entry and old
+ * NetworkStats.Entry, that should be used to copy state from the old to the new.
+ * This is useful for slicing by particular dimensions. For example, if we wished
+ * to slice by uid and tag, we could write the following lambda:
+ * (new, old) -> {
+ * new.uid = old.uid;
+ * new.tag = old.tag;
+ * }
+ * If no slicer is provided, the data is not sliced by any dimensions.
+ * @return new NeworkStats object appropriately sliced
*/
- @NonNull private NetworkStats rollupNetworkStatsByFgbg(@NonNull NetworkStats stats) {
+ @NonNull private NetworkStats sliceNetworkStats(@NonNull NetworkStats stats,
+ @Nullable BiConsumer<NetworkStats.Entry, NetworkStats.Entry> slicer) {
final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
final NetworkStats.Entry entry = new NetworkStats.Entry();
+ entry.uid = NetworkStats.UID_ALL;
entry.iface = NetworkStats.IFACE_ALL;
+ entry.set = NetworkStats.SET_ALL;
entry.tag = NetworkStats.TAG_NONE;
entry.metered = NetworkStats.METERED_ALL;
entry.roaming = NetworkStats.ROAMING_ALL;
+ entry.defaultNetwork = NetworkStats.DEFAULT_NETWORK_ALL;
- int size = stats.size();
- final NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
- for (int i = 0; i < size; i++) {
+ final NetworkStats.Entry recycle = new NetworkStats.Entry(); // used for retrieving values
+ for (int i = 0; i < stats.size(); i++) {
stats.getValues(i, recycle);
+ if (slicer != null) {
+ slicer.accept(entry, recycle);
+ }
- // Skip specific tags, since already counted in TAG_NONE
- if (recycle.tag != NetworkStats.TAG_NONE) continue;
-
- entry.set = recycle.set; // Allows slicing by background/foreground
- entry.uid = recycle.uid;
entry.rxBytes = recycle.rxBytes;
entry.rxPackets = recycle.rxPackets;
entry.txBytes = recycle.txBytes;
@@ -987,6 +1100,19 @@
);
}
+ private void registerBytesTransferByTagAndMetered() {
+ int tagId = FrameworkStatsLog.BYTES_TRANSFER_BY_TAG_AND_METERED;
+ PullAtomMetadata metadata = new PullAtomMetadata.Builder()
+ .setAdditiveFields(new int[] {4, 5, 6, 7})
+ .build();
+ mStatsManager.setPullAtomCallback(
+ tagId,
+ metadata,
+ BackgroundThread.getExecutor(),
+ mStatsCallbackImpl
+ );
+ }
+
private void registerBluetoothBytesTransfer() {
int tagId = FrameworkStatsLog.BLUETOOTH_BYTES_TRANSFER;
PullAtomMetadata metadata = new PullAtomMetadata.Builder()
diff --git a/services/people/java/com/android/server/people/data/ConversationInfo.java b/services/people/java/com/android/server/people/data/ConversationInfo.java
index dc3fa2a..1737828 100644
--- a/services/people/java/com/android/server/people/data/ConversationInfo.java
+++ b/services/people/java/com/android/server/people/data/ConversationInfo.java
@@ -142,9 +142,12 @@
return hasShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED);
}
- /** Whether the shortcut for this conversation is cached in Shortcut Service. */
- public boolean isShortcutCached() {
- return hasShortcutFlags(ShortcutInfo.FLAG_CACHED);
+ /**
+ * Whether the shortcut for this conversation is cached in Shortcut Service, with cache owner
+ * set as notifications.
+ */
+ public boolean isShortcutCachedForNotification() {
+ return hasShortcutFlags(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
}
/** Whether this conversation is marked as important by the user. */
@@ -223,7 +226,7 @@
if (isShortcutLongLived()) {
sb.append("Liv");
}
- if (isShortcutCached()) {
+ if (isShortcutCachedForNotification()) {
sb.append("Cac");
}
sb.append("]");
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index bbb0215..63b7162 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -294,14 +294,14 @@
if (notificationListener != null) {
String packageName = packageData.getPackageName();
packageData.forAllConversations(conversationInfo -> {
- if (conversationInfo.isShortcutCached()
+ if (conversationInfo.isShortcutCachedForNotification()
&& conversationInfo.getNotificationChannelId() == null
&& !notificationListener.hasActiveNotifications(
packageName, conversationInfo.getShortcutId())) {
mShortcutServiceInternal.uncacheShortcuts(userId,
mContext.getPackageName(), packageName,
Collections.singletonList(conversationInfo.getShortcutId()),
- userId);
+ userId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
}
});
}
@@ -821,12 +821,12 @@
// The shortcut was cached by Notification Manager synchronously when the
// associated notification was posted. Uncache it here when all the
// associated notifications are removed.
- if (conversationInfo.isShortcutCached()
+ if (conversationInfo.isShortcutCachedForNotification()
&& conversationInfo.getNotificationChannelId() == null) {
mShortcutServiceInternal.uncacheShortcuts(mUserId,
mContext.getPackageName(), sbn.getPackageName(),
Collections.singletonList(conversationInfo.getShortcutId()),
- mUserId);
+ mUserId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
}
} else {
mActiveNotifCounts.put(conversationKey, count);
@@ -891,12 +891,12 @@
ConversationInfo conversationInfo =
packageData != null ? packageData.getConversationInfo(shortcutId) : null;
if (conversationInfo != null
- && conversationInfo.isShortcutCached()
+ && conversationInfo.isShortcutCachedForNotification()
&& conversationInfo.getNotificationChannelId() == null) {
mShortcutServiceInternal.uncacheShortcuts(mUserId,
mContext.getPackageName(), packageName,
Collections.singletonList(shortcutId),
- mUserId);
+ mUserId, ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
index 70d6cf8..c5d9487 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/ConversationInfoTest.java
@@ -46,7 +46,8 @@
.setContactUri(CONTACT_URI)
.setContactPhoneNumber(PHONE_NUMBER)
.setNotificationChannelId(NOTIFICATION_CHANNEL_ID)
- .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED | ShortcutInfo.FLAG_CACHED)
+ .setShortcutFlags(ShortcutInfo.FLAG_LONG_LIVED
+ | ShortcutInfo.FLAG_CACHED_NOTIFICATIONS)
.setImportant(true)
.setNotificationSilenced(true)
.setBubbled(true)
@@ -62,7 +63,7 @@
assertEquals(PHONE_NUMBER, conversationInfo.getContactPhoneNumber());
assertEquals(NOTIFICATION_CHANNEL_ID, conversationInfo.getNotificationChannelId());
assertTrue(conversationInfo.isShortcutLongLived());
- assertTrue(conversationInfo.isShortcutCached());
+ assertTrue(conversationInfo.isShortcutCachedForNotification());
assertTrue(conversationInfo.isImportant());
assertTrue(conversationInfo.isNotificationSilenced());
assertTrue(conversationInfo.isBubbled());
@@ -84,7 +85,7 @@
assertNull(conversationInfo.getContactPhoneNumber());
assertNull(conversationInfo.getNotificationChannelId());
assertFalse(conversationInfo.isShortcutLongLived());
- assertFalse(conversationInfo.isShortcutCached());
+ assertFalse(conversationInfo.isShortcutCachedForNotification());
assertFalse(conversationInfo.isImportant());
assertFalse(conversationInfo.isNotificationSilenced());
assertFalse(conversationInfo.isBubbled());
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 1a2032a..b2f7abb 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -405,7 +405,7 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- shortcut.setCached();
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
NotificationListenerService listenerService =
@@ -419,7 +419,8 @@
assertEquals(1, activeNotificationOpenTimeSlots.size());
verify(mShortcutServiceInternal).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME),
- eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY));
+ eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
}
@Test
@@ -434,7 +435,7 @@
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
// Post one notification.
- shortcut.setCached();
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
listenerService.onNotificationPosted(mStatusBarNotification);
@@ -445,14 +446,15 @@
listenerService.onNotificationRemoved(mStatusBarNotification, null,
NotificationListenerService.REASON_CANCEL);
verify(mShortcutServiceInternal, never()).uncacheShortcuts(
- anyInt(), any(), anyString(), any(), anyInt());
+ anyInt(), any(), anyString(), any(), anyInt(), anyInt());
// Removing the second notification un-caches the shortcut.
listenerService.onNotificationRemoved(mStatusBarNotification, null,
NotificationListenerService.REASON_CANCEL_ALL);
verify(mShortcutServiceInternal).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME),
- eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY));
+ eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
}
@Test
@@ -467,7 +469,7 @@
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
listenerService.onNotificationPosted(mStatusBarNotification);
- shortcut.setCached();
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
@@ -477,7 +479,8 @@
NotificationListenerService.REASON_CANCEL_ALL);
verify(mShortcutServiceInternal, never()).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME),
- eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY));
+ eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
}
@Test
@@ -569,13 +572,14 @@
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
listenerService.onNotificationPosted(mStatusBarNotification);
- shortcut.setCached();
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
mShutdownBroadcastReceiver.onReceive(mContext, new Intent());
verify(mShortcutServiceInternal).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME),
- eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY));
+ eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
}
@Test
@@ -590,7 +594,7 @@
mDataManager.getNotificationListenerServiceForTesting(USER_ID_PRIMARY);
listenerService.onNotificationPosted(mStatusBarNotification);
- shortcut.setCached();
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
listenerService.onNotificationChannelModified(TEST_PKG_NAME, UserHandle.of(USER_ID_PRIMARY),
@@ -599,7 +603,8 @@
mShutdownBroadcastReceiver.onReceive(mContext, new Intent());
verify(mShortcutServiceInternal, never()).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME),
- eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY));
+ eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
}
@Test
@@ -767,14 +772,15 @@
ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
buildPerson());
- shortcut.setCached();
+ shortcut.setCached(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS);
mDataManager.addOrUpdateConversationInfo(shortcut);
mDataManager.pruneDataForUser(USER_ID_PRIMARY, mCancellationSignal);
verify(mShortcutServiceInternal).uncacheShortcuts(
anyInt(), any(), eq(TEST_PKG_NAME),
- eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY));
+ eq(Collections.singletonList(TEST_SHORTCUT_ID)), eq(USER_ID_PRIMARY),
+ eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index db02524..90989b9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -137,6 +137,9 @@
@SmallTest
public class ShortcutManagerTest1 extends BaseShortcutManagerTest {
+ private static final int CACHE_OWNER_0 = LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
+ private static final int CACHE_OWNER_1 = LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
+
@Override
protected void tearDown() throws Exception {
deleteUriFile("file32x32.jpg");
@@ -487,7 +490,8 @@
mManager.pushDynamicShortcut(s8);
assertEquals(4, getCallerShortcut("s8").getRank());
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s8"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s8"), HANDLE_USER_0,
+ CACHE_OWNER_0);
});
mManager.pushDynamicShortcut(s9);
@@ -1452,8 +1456,10 @@
// Cache 1 and 2
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"),
- HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"),
+ HANDLE_USER_0, CACHE_OWNER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"),
+ HANDLE_USER_0, CACHE_OWNER_1);
});
setCaller(CALLING_PACKAGE_1);
@@ -1532,8 +1538,10 @@
// Cache some, but non long lived shortcuts will be ignored.
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s4"),
- HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"),
+ HANDLE_USER_0, CACHE_OWNER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
+ HANDLE_USER_0, CACHE_OWNER_1);
});
setCaller(CALLING_PACKAGE_1);
@@ -1555,10 +1563,18 @@
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
"s2", "s4");
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s2", "s4"),
+ HANDLE_USER_0, CACHE_OWNER_0);
+ });
+ // s2 still cached by owner1. s4 wasn't cached by owner0 so didn't get removed.
+ assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
+ "s2", "s4");
+
// uncache a non-dynamic shortcut. Should be removed.
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s4"),
- HANDLE_USER_0);
+ HANDLE_USER_0, CACHE_OWNER_1);
});
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
"s2");
@@ -1566,7 +1582,7 @@
// Cache another shortcut
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s3"),
- HANDLE_USER_0);
+ HANDLE_USER_0, CACHE_OWNER_0);
});
assertShortcutIds(mManager.getShortcuts(ShortcutManager.FLAG_MATCH_CACHED),
"s2", "s3");
@@ -1594,7 +1610,7 @@
// Cache All
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3", "s4"),
- HANDLE_USER_0);
+ HANDLE_USER_0, CACHE_OWNER_0);
});
setCaller(CALLING_PACKAGE_1);
@@ -1792,8 +1808,10 @@
setCaller(LAUNCHER_1);
// Cache some shortcuts. Only long lived shortcuts can get cached.
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"), getCallingUser());
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_3, list("s3"), getCallingUser());
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"), getCallingUser(),
+ CACHE_OWNER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_3, list("s3"), getCallingUser(),
+ CACHE_OWNER_0);
// Cached ones only
assertShortcutIds(assertAllNotKeyFieldsOnly(
@@ -8732,7 +8750,8 @@
assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s3", USER_0,
filter_any));
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"), HANDLE_USER_0,
+ CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java
index 6219665..6a2b8e0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest11.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.verify;
import android.content.ComponentName;
+import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutChangeCallback;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.ShortcutInfo;
@@ -46,6 +47,9 @@
private static final ShortcutQuery QUERY_MATCH_ALL = createShortcutQuery(
ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED);
+ private static final int CACHE_OWNER_0 = LauncherApps.FLAG_CACHE_NOTIFICATION_SHORTCUTS;
+ private static final int CACHE_OWNER_1 = LauncherApps.FLAG_CACHE_BUBBLE_SHORTCUTS;
+
private final TestLooper mTestLooper = new TestLooper();
public void testShortcutChangeCallback_setDynamicShortcuts() {
@@ -113,7 +117,8 @@
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0);
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
+ CACHE_OWNER_0);
});
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
@@ -211,7 +216,42 @@
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
mTestLooper.getNewExecutor());
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s3"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s3"), HANDLE_USER_0,
+ CACHE_OWNER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s3"), HANDLE_USER_0,
+ CACHE_OWNER_1);
+ });
+
+ mTestLooper.dispatchAll();
+
+ ArgumentCaptor<List> shortcuts = ArgumentCaptor.forClass(List.class);
+ verify(callback, times(2)).onShortcutsAddedOrUpdated(
+ eq(CALLING_PACKAGE_1), shortcuts.capture(), eq(HANDLE_USER_0));
+ verify(callback, times(0)).onShortcutsRemoved(any(), any(), any());
+
+ assertWith(shortcuts.getValue())
+ .areAllWithKeyFieldsOnly()
+ .haveIds("s1", "s3");
+ }
+
+ public void testShortcutChangeCallback_cacheShortcuts_alreadyCached() {
+ runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
+ assertTrue(mManager.setDynamicShortcuts(list(makeLongLivedShortcut("s1"),
+ makeLongLivedShortcut("s2"), makeLongLivedShortcut("s3"))));
+ });
+
+ ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
+ runWithCaller(LAUNCHER_1, USER_0, () -> {
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s3"), HANDLE_USER_0,
+ CACHE_OWNER_0);
+ mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
+ mTestLooper.getNewExecutor());
+ // Should not cause any callback events
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s3"), HANDLE_USER_0,
+ CACHE_OWNER_0);
+ // Should cause a change event
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s3"), HANDLE_USER_0,
+ CACHE_OWNER_1);
});
mTestLooper.dispatchAll();
@@ -234,10 +274,12 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2"), HANDLE_USER_0,
+ CACHE_OWNER_0);
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
mTestLooper.getNewExecutor());
- mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
+ mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
+ CACHE_OWNER_0);
});
mTestLooper.dispatchAll();
@@ -259,8 +301,11 @@
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2", "s3"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3"), HANDLE_USER_0,
+ CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s1"), HANDLE_USER_0,
+ CACHE_OWNER_1);
});
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
@@ -271,7 +316,8 @@
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
mTestLooper.getNewExecutor());
- mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s2", "s3"), HANDLE_USER_0);
+ mLauncherApps.uncacheShortcuts(CALLING_PACKAGE_1, list("s1", "s2", "s3"), HANDLE_USER_0,
+ CACHE_OWNER_0);
});
mTestLooper.dispatchAll();
@@ -284,9 +330,10 @@
verify(callback, times(1)).onShortcutsRemoved(
eq(CALLING_PACKAGE_1), removedShortcuts.capture(), eq(HANDLE_USER_0));
+ // s1 is still cached for owner1, s2 is pinned.
assertWith(changedShortcuts.getValue())
.areAllWithKeyFieldsOnly()
- .haveIds("s2");
+ .haveIds("s1", "s2");
assertWith(removedShortcuts.getValue())
.areAllWithKeyFieldsOnly()
@@ -453,7 +500,8 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0,
+ CACHE_OWNER_0);
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
mTestLooper.getNewExecutor());
});
@@ -511,7 +559,8 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
+ CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
mTestLooper.getNewExecutor());
@@ -547,7 +596,8 @@
});
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
+ CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
});
@@ -614,7 +664,8 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
+ CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
mTestLooper.getNewExecutor());
@@ -680,7 +731,8 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
+ CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
mTestLooper.getNewExecutor());
@@ -747,7 +799,8 @@
ShortcutChangeCallback callback = mock(ShortcutChangeCallback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
- mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0);
+ mLauncherApps.cacheShortcuts(CALLING_PACKAGE_1, list("s2"), HANDLE_USER_0,
+ CACHE_OWNER_0);
mLauncherApps.pinShortcuts(CALLING_PACKAGE_1, list("s3"), HANDLE_USER_0);
mLauncherApps.registerShortcutChangeCallback(callback, QUERY_MATCH_ALL,
mTestLooper.getNewExecutor());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 2bea491..cf63682 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6152,7 +6152,7 @@
// Make sure the shortcut is cached.
verify(mShortcutServiceInternal).cacheShortcuts(
anyInt(), any(), eq(PKG), eq(Collections.singletonList(VALID_CONVO_SHORTCUT_ID)),
- eq(USER_SYSTEM));
+ eq(USER_SYSTEM), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
// Test: Remove the shortcut
when(mLauncherApps.getShortcuts(any(), any())).thenReturn(null);
@@ -6225,7 +6225,7 @@
// Make sure the shortcut is cached.
verify(mShortcutServiceInternal).cacheShortcuts(
anyInt(), any(), eq(PKG), eq(Collections.singletonList(shortcutId)),
- eq(USER_SYSTEM));
+ eq(USER_SYSTEM), eq(ShortcutInfo.FLAG_CACHED_NOTIFICATIONS));
// Test: Remove the notification
mBinderService.cancelNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 43db1d9ce..f6c14e6 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -518,9 +518,6 @@
* @param executor The executor the callback events should be run on.
* @param c The MmTel {@link CapabilityCallback} to be registered.
* @see #unregisterMmTelCapabilityCallback(CapabilityCallback)
- * @throws IllegalArgumentException if the subscription associated with this callback is not
- * active (SIM is not inserted, ESIM inactive) or invalid, or a null {@link Executor} or
- * {@link CapabilityCallback} callback.
* @throws ImsException if the subscription associated with this callback is valid, but
* the {@link ImsService} associated with the subscription is not available. This can happen if
* the service crashed, for example. See {@link ImsException#getCode()} for a more detailed
@@ -543,18 +540,13 @@
ITelephony iTelephony = getITelephony();
if (iTelephony == null) {
throw new ImsException("Could not find Telephony Service.",
- ImsException.CODE_ERROR_INVALID_SUBSCRIPTION);
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
try {
iTelephony.registerMmTelCapabilityCallback(mSubId, c.getBinder());
} catch (ServiceSpecificException e) {
- if (e.errorCode == ImsException.CODE_ERROR_INVALID_SUBSCRIPTION) {
- // Rethrow as runtime error to keep API compatible.
- throw new IllegalArgumentException(e.getMessage());
- } else {
- throw new ImsException(e.getMessage(), e.errorCode);
- }
+ throw new ImsException(e.getMessage(), e.errorCode);
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
} catch (IllegalStateException e) {
diff --git a/tests/BlobStoreTestUtils/Android.bp b/tests/BlobStoreTestUtils/Android.bp
index 5c7c68b..53d3638 100644
--- a/tests/BlobStoreTestUtils/Android.bp
+++ b/tests/BlobStoreTestUtils/Android.bp
@@ -18,6 +18,7 @@
static_libs: [
"truth-prebuilt",
"androidx.test.uiautomator_uiautomator",
+ "androidx.test.ext.junit",
],
sdk_version: "test_current",
}
\ No newline at end of file
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
index 371375c..4a0ca66 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
@@ -42,6 +42,7 @@
private final File mFile;
private final long mFileSize;
private final CharSequence mLabel;
+ private final long mExpiryDurationMs;
byte[] mFileDigest;
long mExpiryTimeMs;
@@ -51,6 +52,7 @@
mFile = new File(builder.getContext().getFilesDir(), builder.getFileName());
mFileSize = builder.getFileSize();
mLabel = builder.getLabel();
+ mExpiryDurationMs = builder.getExpiryDurationMs();
}
public static class Builder {
@@ -59,6 +61,7 @@
private long mFileSize = DEFAULT_SIZE_BYTES;
private CharSequence mLabel = "Test label";
private String mFileName = "blob_" + System.nanoTime();
+ private long mExpiryDurationMs = TimeUnit.DAYS.toMillis(1);
public Builder(Context context) {
mContext = context;
@@ -104,6 +107,15 @@
return mFileName;
}
+ public Builder setExpiryDurationMs(long durationMs) {
+ mExpiryDurationMs = durationMs;
+ return this;
+ }
+
+ public long getExpiryDurationMs() {
+ return mExpiryDurationMs;
+ }
+
public DummyBlobData build() {
return new DummyBlobData(this);
}
@@ -114,7 +126,7 @@
writeRandomData(file, mFileSize);
}
mFileDigest = FileUtils.digest(mFile, "SHA-256");
- mExpiryTimeMs = System.currentTimeMillis() + TimeUnit.DAYS.toMillis(1);
+ mExpiryTimeMs = System.currentTimeMillis() + mExpiryDurationMs;
}
public BlobHandle getBlobHandle() throws Exception {
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
index 7cf58e1..b9bd661 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
@@ -18,7 +18,6 @@
import static com.google.common.truth.Truth.assertThat;
-import android.app.Instrumentation;
import android.app.blob.BlobHandle;
import android.app.blob.BlobStoreManager;
import android.app.blob.LeaseInfo;
@@ -27,6 +26,7 @@
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.UiDevice;
import java.io.FileInputStream;
@@ -149,14 +149,14 @@
assertThat(leaseInfo.getDescription()).isEqualTo(description);
}
- public static void triggerIdleMaintenance(Instrumentation instrumentation) throws IOException {
- runShellCmd(instrumentation, "cmd blob_store idle-maintenance");
+ public static void triggerIdleMaintenance() throws IOException {
+ runShellCmd("cmd blob_store idle-maintenance");
}
- private static String runShellCmd(Instrumentation instrumentation,
- String cmd) throws IOException {
- final UiDevice uiDevice = UiDevice.getInstance(instrumentation);
- final String result = uiDevice.executeShellCommand(cmd);
+ public static String runShellCmd(String cmd) throws IOException {
+ final UiDevice uiDevice = UiDevice.getInstance(
+ InstrumentationRegistry.getInstrumentation());
+ final String result = uiDevice.executeShellCommand(cmd).trim();
Log.i(TAG, "Output of '" + cmd + "': '" + result + "'");
return result;
}
diff --git a/tests/net/java/com/android/internal/net/VpnProfileTest.java b/tests/net/java/com/android/internal/net/VpnProfileTest.java
index ceca6f0..e5daa71 100644
--- a/tests/net/java/com/android/internal/net/VpnProfileTest.java
+++ b/tests/net/java/com/android/internal/net/VpnProfileTest.java
@@ -33,7 +33,9 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/** Unit tests for {@link VpnProfile}. */
@SmallTest
@@ -41,6 +43,9 @@
public class VpnProfileTest {
private static final String DUMMY_PROFILE_KEY = "Test";
+ private static final int ENCODED_INDEX_AUTH_PARAMS_INLINE = 23;
+ private static final int ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS = 24;
+
@Test
public void testDefaults() throws Exception {
final VpnProfile p = new VpnProfile(DUMMY_PROFILE_KEY);
@@ -67,10 +72,11 @@
assertFalse(p.isMetered);
assertEquals(1360, p.maxMtu);
assertFalse(p.areAuthParamsInline);
+ assertFalse(p.isRestrictedToTestNetworks);
}
private VpnProfile getSampleIkev2Profile(String key) {
- final VpnProfile p = new VpnProfile(key);
+ final VpnProfile p = new VpnProfile(key, true /* isRestrictedToTestNetworks */);
p.name = "foo";
p.type = VpnProfile.TYPE_IKEV2_IPSEC_USER_PASS;
@@ -116,7 +122,7 @@
@Test
public void testParcelUnparcel() {
- assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 22);
+ assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23);
}
@Test
@@ -159,14 +165,41 @@
assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooManyValues));
}
+ private String getEncodedDecodedIkev2ProfileMissingValues(int... missingIndices) {
+ // Sort to ensure when we remove, we can do it from greatest first.
+ Arrays.sort(missingIndices);
+
+ final String encoded = new String(getSampleIkev2Profile(DUMMY_PROFILE_KEY).encode());
+ final List<String> parts =
+ new ArrayList<>(Arrays.asList(encoded.split(VpnProfile.VALUE_DELIMITER)));
+
+ // Remove from back first to ensure indexing is consistent.
+ for (int i = missingIndices.length - 1; i >= 0; i--) {
+ parts.remove(missingIndices[i]);
+ }
+
+ return String.join(VpnProfile.VALUE_DELIMITER, parts.toArray(new String[0]));
+ }
+
@Test
public void testEncodeDecodeInvalidNumberOfValues() {
- final VpnProfile profile = getSampleIkev2Profile(DUMMY_PROFILE_KEY);
- final String encoded = new String(profile.encode());
- final byte[] tooFewValues =
- encoded.substring(0, encoded.lastIndexOf(VpnProfile.VALUE_DELIMITER)).getBytes();
+ final String tooFewValues =
+ getEncodedDecodedIkev2ProfileMissingValues(
+ ENCODED_INDEX_AUTH_PARAMS_INLINE,
+ ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */);
- assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues));
+ assertNull(VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes()));
+ }
+
+ @Test
+ public void testEncodeDecodeMissingIsRestrictedToTestNetworks() {
+ final String tooFewValues =
+ getEncodedDecodedIkev2ProfileMissingValues(
+ ENCODED_INDEX_RESTRICTED_TO_TEST_NETWORKS /* missingIndices */);
+
+ // Verify decoding without isRestrictedToTestNetworks defaults to false
+ final VpnProfile decoded = VpnProfile.decode(DUMMY_PROFILE_KEY, tooFewValues.getBytes());
+ assertFalse(decoded.isRestrictedToTestNetworks);
}
@Test