diff options
101 files changed, 1987 insertions, 830 deletions
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 49b3ec1d113b..cea7fcca6e20 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 @@ class BlobMetadata { } 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 7a27b2c795fb..a2bce31fc7b2 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 @@ public class BlobStoreManagerService extends SystemService { } } + 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 72af323e9d5f..a4a2e80c195a 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 @@ class BlobStoreManagerShellCommand extends ShellCommand { return runDeleteBlob(pw); case "idle-maintenance": return runIdleMaintenance(pw); + case "query-blob-existence": + return runQueryBlobExistence(pw); default: return handleDefaultCommands(cmd); } @@ -91,6 +93,16 @@ class BlobStoreManagerShellCommand extends ShellCommand { 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 @@ class BlobStoreManagerShellCommand extends ShellCommand { 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 @@ class BlobStoreManagerShellCommand extends ShellCommand { 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 @@ class BlobStoreManagerShellCommand extends ShellCommand { 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 4ffa040fafd4..47bab2947aaf 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -1298,7 +1298,6 @@ Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experiment 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 361b161c76ac..fd883c29dba0 100644 --- a/cmds/statsd/src/shell/ShellSubscriber.cpp +++ b/cmds/statsd/src/shell/ShellSubscriber.cpp @@ -41,13 +41,8 @@ void ShellSubscriber::startNewSubscription(int in, int out, int timeoutSec) { { 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::startNewSubscription(int in, int out, int timeoutSec) { } } -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 @@ bool ShellSubscriber::readConfig(shared_ptr<SubscriptionInfo> subscriptionInfo) 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 @@ bool ShellSubscriber::readConfig(shared_ptr<SubscriptionInfo> subscriptionInfo) 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 @@ void ShellSubscriber::startPull(int myToken) { 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 @@ void ShellSubscriber::writePulledAtomsLocked(const vector<std::shared_ptr<LogEve } } - if (count > 0) attemptWriteToSocketLocked(mProto.size()); + if (count > 0) attemptWriteToPipeLocked(mProto.size()); } void ShellSubscriber::onLogEvent(const LogEvent& event) { @@ -214,26 +214,24 @@ void ShellSubscriber::onLogEvent(const LogEvent& event) { 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 @@ void ShellSubscriber::attemptWriteToSocketLocked(size_t dataSize) { 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 26c8a2a0b683..4c05fa7f71c2 100644 --- a/cmds/statsd/src/shell/ShellSubscriber.h +++ b/cmds/statsd/src/shell/ShellSubscriber.h @@ -92,7 +92,6 @@ private: int mOutputFd; std::vector<SimpleAtomMatcher> mPushedMatchers; std::vector<PullInfo> mPulledInfo; - int mPullIntervalMin; bool mClientAlive; }; @@ -100,27 +99,25 @@ private: 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/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java index 01cf2b94a842..58068769e63a 100644 --- a/core/java/android/app/PropertyInvalidatedCache.java +++ b/core/java/android/app/PropertyInvalidatedCache.java @@ -188,6 +188,17 @@ public abstract class PropertyInvalidatedCache<Query, Result> { 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 @@ public abstract class PropertyInvalidatedCache<Query, Result> { }; synchronized (sCorkLock) { sCaches.put(this, null); + sInvalidates.put(propertyName, (long) 0); } } @@ -365,6 +377,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { synchronized (mLock) { if (currentNonce == mLastSeenNonce) { cachedResult = mCache.get(query); + + if (cachedResult != null) mHits++; } else { if (DEBUG) { Log.d(TAG, @@ -428,6 +442,7 @@ public abstract class PropertyInvalidatedCache<Query, Result> { if (mLastSeenNonce == currentNonce && result != null) { mCache.put(query, result); } + mMisses++; } return maybeCheckConsistency(query, result); } @@ -531,6 +546,8 @@ public abstract class PropertyInvalidatedCache<Query, Result> { newValueString)); } SystemProperties.set(name, newValueString); + long invalidateCount = sInvalidates.getOrDefault(name, (long) 0); + sInvalidates.put(name, ++invalidateCount); } /** @@ -758,8 +775,16 @@ public abstract class PropertyInvalidatedCache<Query, Result> { } 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 aa290404c001..389458b64fc1 100644 --- a/core/java/android/content/pm/ILauncherApps.aidl +++ b/core/java/android/content/pm/ILauncherApps.aidl @@ -99,9 +99,9 @@ interface ILauncherApps { 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 4299e805264c..bd1ee27ece9e 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -155,6 +155,26 @@ public class LauncherApps { 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 @@ public class LauncherApps { * @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 @@ public class LauncherApps { */ @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 @@ public class LauncherApps { * @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 @@ public class LauncherApps { */ @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 dcc6cb25c95b..1b3c46f90851 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -119,12 +119,27 @@ public final class ShortcutInfo implements Parcelable { /** @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 @@ public final class ShortcutInfo implements Parcelable { 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 @@ public final class ShortcutInfo implements Parcelable { } /** @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 @@ public final class ShortcutInfo implements Parcelable { /** @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 eee91ce173dc..c62767ee031b 100644 --- a/core/java/android/content/pm/ShortcutServiceInternal.java +++ b/core/java/android/content/pm/ShortcutServiceInternal.java @@ -92,10 +92,10 @@ public abstract class ShortcutServiceInternal { 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 836624beb3b2..407ff04dc4a3 100644 --- a/core/java/android/net/Ikev2VpnProfile.java +++ b/core/java/android/net/Ikev2VpnProfile.java @@ -101,6 +101,7 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { @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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { mIsBypassable = isBypassable; mIsMetered = isMetered; mMaxMtu = maxMtu; + mIsRestrictedToTestNetworks = restrictToTestNetworks; validate(); } @@ -329,6 +332,15 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { mAllowedAlgorithms, mIsBypassable, mIsMetered, - mMaxMtu); + mMaxMtu, + mIsRestrictedToTestNetworks); } @Override @@ -368,7 +381,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { && Objects.equals(mAllowedAlgorithms, other.mAllowedAlgorithms) && mIsBypassable == other.mIsBypassable && mIsMetered == other.mIsMetered - && mMaxMtu == other.mMaxMtu; + && mMaxMtu == other.mMaxMtu + && mIsRestrictedToTestNetworks == other.mIsRestrictedToTestNetworks; } /** @@ -381,7 +395,8 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { */ @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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { } /** + * 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 @@ public final class Ikev2VpnProfile extends PlatformVpnProfile { mAllowedAlgorithms, mIsBypassable, mIsMetered, - mMaxMtu); + mMaxMtu, + mIsRestrictedToTestNetworks); } } } diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index 2a6d6ed7d3be..7845200f4bf7 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -4102,12 +4102,25 @@ public class UserManager { } /** - * Returns true if the user switcher should be shown, this will be if device supports multi-user - * and there are at least 2 users available that are not managed profiles. - * @hide + * Returns true if the user switcher should be shown. + * I.e., returns whether the user switcher is enabled and there is something actionable to show. + * * @return true if user switcher should be shown. + * @hide */ public boolean isUserSwitcherEnabled() { + return isUserSwitcherEnabled(false); + } + + /** + * Returns true if the user switcher should be shown. + * + * @param showEvenIfNotActionable value to return if the feature is enabled but there is nothing + * actionable for the user to do anyway + * @return true if user switcher should be shown. + * @hide + */ + public boolean isUserSwitcherEnabled(boolean showEvenIfNotActionable) { if (!supportsMultipleUsers()) { return false; } @@ -4118,15 +4131,26 @@ public class UserManager { if (isDeviceInDemoMode(mContext)) { return false; } - // If user disabled this feature, don't show switcher - final boolean userSwitcherEnabled = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.USER_SWITCHER_ENABLED, 1) != 0; - if (!userSwitcherEnabled) { + // Check the Settings.Global.USER_SWITCHER_ENABLED that the user can toggle on/off. + final boolean userSwitcherSettingOn = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.USER_SWITCHER_ENABLED, + Resources.getSystem().getBoolean(R.bool.config_showUserSwitcherByDefault) ? 1 : 0) + != 0; + if (!userSwitcherSettingOn) { return false; } - List<UserInfo> users = getUsers(true); + + // The feature is enabled. But is it worth showing? + return showEvenIfNotActionable + || areThereUsersToWhichToSwitch() // There are switchable users. + || !hasUserRestriction(UserManager.DISALLOW_ADD_USER); // New users can be added. + } + + /** Returns whether there are any users (other than the current user) to which to switch. */ + private boolean areThereUsersToWhichToSwitch() { + final List<UserInfo> users = getUsers(true); if (users == null) { - return false; + return false; } int switchableUserCount = 0; for (UserInfo user : users) { @@ -4134,9 +4158,7 @@ public class UserManager { ++switchableUserCount; } } - final boolean guestEnabled = !mContext.getSystemService(DevicePolicyManager.class) - .getGuestUserDisabled(null); - return switchableUserCount > 1 || guestEnabled; + return switchableUserCount > 1; } /** diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 3d95f65df842..ef9edc6c0741 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -70,6 +70,8 @@ import java.util.function.BiFunction; */ public class InsetsController implements WindowInsetsController, InsetsAnimationControlCallbacks { + private int mTypesBeingCancelled; + public interface Host { Handler getHandler(); @@ -743,6 +745,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 0807f4162162..7042f29fc4e4 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -202,9 +202,9 @@ public class WebChromeClient { * <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 @@ public class WebChromeClient { * <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 @@ public class WebChromeClient { * <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 @@ public class WebChromeClient { } /** - * 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 8b0dd8ae18ce..1f25feb1f028 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -171,17 +171,6 @@ public class ChooserActivity extends ResolverActivity implements 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 @@ public class ChooserActivity extends ResolverActivity implements } 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 fca156a97a83..e65d1fe9ce53 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -18,6 +18,8 @@ package com.android.internal.app; 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.ActivityThread; 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 @@ public class IntentForwarderActivity extends Activity { 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 @@ public class IntentForwarderActivity extends Activity { } 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 @@ public class IntentForwarderActivity extends Activity { // 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 @@ public class IntentForwarderActivity extends Activity { 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 182c7f272186..66d850e296e3 100644 --- a/core/java/com/android/internal/app/ResolverActivity.java +++ b/core/java/com/android/internal/app/ResolverActivity.java @@ -179,6 +179,17 @@ public class ResolverActivity extends Activity implements // 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 @@ public class ResolverActivity extends Activity implements 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 @@ public class ResolverActivity extends Activity implements } /** + * 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 829bd8a5a2a7..8ea5aa815a1c 100644 --- a/core/java/com/android/internal/net/VpnProfile.java +++ b/core/java/com/android/internal/net/VpnProfile.java @@ -136,13 +136,19 @@ public final class VpnProfile implements Cloneable, Parcelable { 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 @@ public final class VpnProfile implements Cloneable, Parcelable { isMetered = in.readBoolean(); maxMtu = in.readInt(); areAuthParamsInline = in.readBoolean(); + isRestrictedToTestNetworks = in.readBoolean(); } /** @@ -220,6 +227,7 @@ public final class VpnProfile implements Cloneable, Parcelable { out.writeBoolean(isMetered); out.writeInt(maxMtu); out.writeBoolean(areAuthParamsInline); + out.writeBoolean(isRestrictedToTestNetworks); } /** @@ -237,12 +245,21 @@ public final class VpnProfile implements Cloneable, Parcelable { 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 @@ public final class VpnProfile implements Cloneable, Parcelable { 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 @@ public final class VpnProfile implements Cloneable, Parcelable { 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 @@ public final class VpnProfile implements Cloneable, Parcelable { 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 @@ public final class VpnProfile implements Cloneable, Parcelable { && 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 415e21062aa5..5a1af84eccac 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -9875,6 +9875,10 @@ public class BatteryStatsImpl extends BatteryStats { 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/core/res/res/values-sw600dp/config.xml b/core/res/res/values-sw600dp/config.xml index 368e307f9ff5..34b6a54be493 100644 --- a/core/res/res/values-sw600dp/config.xml +++ b/core/res/res/values-sw600dp/config.xml @@ -48,5 +48,8 @@ <!-- Set to true to enable the user switcher on the keyguard. --> <bool name="config_keyguardUserSwitcher">true</bool> + + <!-- If true, show multiuser switcher by default unless the user specifically disables it. --> + <bool name="config_showUserSwitcherByDefault">true</bool> </resources> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2dc3996e3d7b..9959de8aa645 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4444,6 +4444,9 @@ <!-- Set to true to enable the user switcher on the keyguard. --> <bool name="config_keyguardUserSwitcher">false</bool> + <!-- If true, show multiuser switcher by default unless the user specifically disables it. --> + <bool name="config_showUserSwitcherByDefault">false</bool> + <!-- Set to true to make assistant show in front of the dream/screensaver. --> <bool name="config_assistantOnTopOfDream">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 6ce25d45b16c..61a65249a464 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4006,6 +4006,9 @@ <!-- Set to true to enable the user switcher on the keyguard. --> <java-symbol type="bool" name="config_keyguardUserSwitcher" /> + <!-- If true, show multiuser switcher by default unless the user specifically disables it. --> + <java-symbol type="bool" name="config_showUserSwitcherByDefault" /> + <!-- Set to true to make assistant show in front of the dream/screensaver. --> <java-symbol type="bool" name="config_assistantOnTopOfDream"/> diff --git a/data/sounds/AudioTv.mk b/data/sounds/AudioTv.mk index 2a31e4c2d55a..fd53aff73a50 100644 --- a/data/sounds/AudioTv.mk +++ b/data/sounds/AudioTv.mk @@ -16,6 +16,7 @@ LOCAL_PATH := frameworks/base/data/sounds 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 1b8336f54637..58421ab5ccd9 100644 --- a/graphics/java/android/graphics/pdf/PdfDocument.java +++ b/graphics/java/android/graphics/pdf/PdfDocument.java @@ -46,8 +46,8 @@ import java.util.List; * // 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 aa3279321e90..6141308a8fdb 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 0bbc3e46b044..c3abdc22b19d 100644 --- a/media/jni/soundpool/Sound.cpp +++ b/media/jni/soundpool/Sound.cpp @@ -31,7 +31,7 @@ constexpr size_t kDefaultHeapSize = 1024 * 1024; // 1MB (compatible with low m 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 @@ Sound::~Sound() 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 @@ static status_t decode(int fd, int64_t offset, int64_t length, 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 @@ status_t Sound::doLoad() 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 12200ef03aad..6614fdb5af53 100644 --- a/media/jni/soundpool/SoundDecoder.cpp +++ b/media/jni/soundpool/SoundDecoder.cpp @@ -57,7 +57,7 @@ void SoundDecoder::quit() 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 1288943e86e3..7b62114483cf 100644 --- a/media/jni/soundpool/SoundDecoder.h +++ b/media/jni/soundpool/SoundDecoder.h @@ -30,21 +30,22 @@ class SoundDecoder { 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 3c625bf3bb7f..5b16174eef2b 100644 --- a/media/jni/soundpool/SoundManager.cpp +++ b/media/jni/soundpool/SoundManager.cpp @@ -43,7 +43,7 @@ SoundManager::~SoundManager() 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 9201e78132f4..4a4e3b87be26 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: } 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 e3152d6349aa..a6d975851619 100644 --- a/media/jni/soundpool/Stream.cpp +++ b/media/jni/soundpool/Stream.cpp @@ -105,7 +105,7 @@ void Stream::setRate(int32_t streamID, float rate) 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::stop_l() 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 @@ void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID, 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 @@ void Stream::play_l(const std::shared_ptr<Sound>& sound, int32_t nextStreamID, __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 @@ exit: /* 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 82d2690e2965..fd929210eb46 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 @@ public: // 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; } + + // Can change with setPlay() + int32_t getStreamID() const NO_THREAD_SAFETY_ANALYSIS { return mStreamID; } + + // Can change with play_l() + int32_t getSoundID() const NO_THREAD_SAFETY_ANALYSIS { return mSoundID; } - 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; } + bool hasSound() const NO_THREAD_SAFETY_ANALYSIS { return mSound.get() != nullptr; } - Stream* getPairStream() const; // this never changes. See top of header. + // 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; - - sp<AudioTrack> mAudioTrack; - int mToggle = 0; - int64_t mStopTimeNs = 0; // if nonzero, time to wait for stop. + 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 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 c61221892c28..9ff4254284dc 100644 --- a/media/jni/soundpool/StreamManager.cpp +++ b/media/jni/soundpool/StreamManager.cpp @@ -55,7 +55,7 @@ StreamMap::StreamMap(int32_t streams) { 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 @@ Stream* StreamMap::findStream(int32_t streamID) const 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 @@ int32_t StreamMap::getNextIdForStream(Stream* stream) const { //////////// +// 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 @@ StreamManager::StreamManager( "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 30ad220db05b..59ae2f9d108b 100644 --- a/media/jni/soundpool/StreamManager.h +++ b/media/jni/soundpool/StreamManager.h @@ -183,9 +183,9 @@ private: 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 @@ private: 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 @@ public: // 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 @@ public: 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 @@ private: } // 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 @@ private: // 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 @@ private: // 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 f6706369f379..8f6df3db718b 100644 --- a/media/jni/soundpool/android_media_SoundPool.cpp +++ b/media/jni/soundpool/android_media_SoundPool.cpp @@ -52,7 +52,7 @@ android_media_SoundPool_load_FD(JNIEnv *env, jobject thiz, jobject fileDescripto { 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 @@ static jboolean 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 @@ android_media_SoundPool_play(JNIEnv *env, jobject thiz, jint sampleID, { 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 @@ android_media_SoundPool_pause(JNIEnv *env, jobject thiz, jint channelID) { 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 @@ android_media_SoundPool_resume(JNIEnv *env, jobject thiz, jint channelID) { 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 @@ android_media_SoundPool_autoPause(JNIEnv *env, jobject thiz) { ALOGV("android_media_SoundPool_autoPause"); SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return; + if (ap == nullptr) return; ap->autoPause(); } @@ -108,7 +108,7 @@ android_media_SoundPool_autoResume(JNIEnv *env, jobject thiz) { ALOGV("android_media_SoundPool_autoResume"); SoundPool *ap = MusterSoundPool(env, thiz); - if (ap == NULL) return; + if (ap == nullptr) return; ap->autoResume(); } @@ -117,7 +117,7 @@ android_media_SoundPool_stop(JNIEnv *env, jobject thiz, jint channelID) { 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 @@ android_media_SoundPool_setVolume(JNIEnv *env, jobject thiz, jint channelID, { 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 @@ android_media_SoundPool_mute(JNIEnv *env, jobject thiz, jboolean muting) { 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 @@ android_media_SoundPool_setPriority(JNIEnv *env, jobject thiz, jint channelID, { 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 @@ android_media_SoundPool_setLoop(JNIEnv *env, jobject thiz, jint channelID, { 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 @@ android_media_SoundPool_setRate(JNIEnv *env, jobject thiz, jint channelID, { 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 @@ static void android_media_callback(SoundPoolEvent event, SoundPool* soundPool, v { 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 @@ android_media_SoundPool_native_setup(JNIEnv *env, jobject thiz, jobject weakRef, 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 @@ android_media_SoundPool_release(JNIEnv *env, jobject thiz) { 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 @@ static const char* const kClassPathName = "android/media/SoundPool"; jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) { - JNIEnv* env = NULL; + JNIEnv* env = nullptr; jint result = -1; jclass clazz; @@ -317,23 +319,23 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) 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 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) // 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 @@ jint JNI_OnLoad(JavaVM* vm, void* /* reserved */) 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 a13451fc2ce0..7f1763d2dec1 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 f7f1ef6f3746..21a2435c0448 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 ecd8b4585609..15eda0689101 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.Notification; 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 @@ import java.util.Objects; 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 @@ class Bubble implements BubbleViewProvider { 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 @@ class Bubble implements BubbleViewProvider { mKey = e.getKey(); mLastUpdated = e.getSbn().getPostTime(); mSuppressionListener = listener; + mFlags = e.getSbn().getNotification().flags; } @Override @@ -118,12 +132,22 @@ class Bubble implements BubbleViewProvider { 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 @@ class Bubble implements BubbleViewProvider { 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 @@ class Bubble implements BubbleViewProvider { 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 @@ class Bubble implements BubbleViewProvider { context, stackView, iconFactory, + skipInflation, callback); if (mInflateSynchronously) { mInflationTask.onPostExecute(mInflationTask.doInBackground()); @@ -327,6 +365,7 @@ class Bubble implements BubbleViewProvider { * Whether this notification should be shown in the shade. */ boolean showInShade() { + if (mEntry == null) return false; return !shouldSuppressNotification() || !mEntry.isClearable(); } @@ -334,8 +373,8 @@ class Bubble implements BubbleViewProvider { * 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 @@ class Bubble implements BubbleViewProvider { */ @Override public boolean showDot() { + if (mEntry == null) return false; return mShowBubbleUpdateDot && !mEntry.shouldSuppressNotificationDot() && !shouldSuppressNotification(); @@ -375,6 +415,7 @@ class Bubble implements BubbleViewProvider { * 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 @@ class Bubble implements BubbleViewProvider { } 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 @@ class Bubble implements BubbleViewProvider { } 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 @@ class Bubble implements BubbleViewProvider { * 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 @@ class Bubble implements BubbleViewProvider { 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 @@ class Bubble implements BubbleViewProvider { } 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 @@ class Bubble implements BubbleViewProvider { 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 5f157c104200..d447596d7cbf 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.LOCAL_VARIABLE; 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.Retention; 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * @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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** - * 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi && 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } - 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } + 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // 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 @@ public class BubbleController implements ConfigurationController.ConfigurationLi 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 35647b0bb2f1..857f1dd3533c 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.DEBUG_BUBBLE_DATA; 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 @@ public class BubbleData { notificationEntryUpdated(bubble, false /* suppressFlyout */, true /* showInShade */); }, - mContext, stack, factory); + mContext, stack, factory, false /* skipInflation */); } void setShowingOverflow(boolean showingOverflow) { @@ -268,7 +269,8 @@ public class BubbleData { } 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 @@ public class BubbleData { 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 @@ public class BubbleData { 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 @@ public class BubbleData { 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 @@ public class BubbleData { 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 ba93f4125ea5..1c5e98bbea07 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 @@ internal class BubbleDataRepository @Inject constructor( 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 @@ internal class BubbleDataRepository @Inject constructor( /** * 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 @@ internal class BubbleDataRepository @Inject constructor( 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 @@ internal class BubbleDataRepository @Inject constructor( 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 c4b4f4316d93..494a51d52095 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.Dependency; 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 @@ public class BubbleExpandedView extends LinearLayout { // 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 @@ public class BubbleExpandedView extends LinearLayout { } 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 @@ public class BubbleExpandedView extends LinearLayout { 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 8fec3385d491..2109a7be9ca8 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_BUBBLES; 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 @@ class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.V 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 418cc505daa8..6ba1aa80ac75 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.Animator; 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.RectF; 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 @@ public class BubbleStackView extends FrameLayout 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 @@ public class BubbleStackView extends FrameLayout 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 @@ public class BubbleStackView extends FrameLayout 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 @@ public class BubbleStackView extends FrameLayout * @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 8a57a735f6cb..525d5b56cc8e 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.Drawable; 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 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask private WeakReference<Context> mContext; private WeakReference<BubbleStackView> mStackView; private BubbleIconFactory mIconFactory; + private boolean mSkipInflation; private Callback mCallback; /** @@ -84,17 +86,20 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask 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 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask @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 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask 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 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask 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 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask // 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 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask 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 4690a8e14072..43482616da2c 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 @@ import android.annotation.UserIdInt 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 821b64ce5e6e..1df9f72022f0 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 TAG_BUBBLE = "bb" 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 @@ fun writeXml(stream: OutputStream, bubbles: List<BubbleEntity>) { /** * 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 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) { 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 @@ private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? { 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 000000000000..9a5b96078e95 --- /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 000000000000..dd061c526c40 --- /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 5765be57b5b0..4760d291072e 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.ControlsListingController 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 0d23557ffa9e..bf84d77224b1 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 @@ class ControlsRequestReceiver : BroadcastReceiver() { } 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 ab3329122edc..606e94760946 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.dagger.qualifiers.Main 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 @@ class ControlsUiControllerImpl @Inject constructor ( @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 61c9a967cc4c..d66b9ac3d21a 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.broadcast.BroadcastDispatcher; 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 com.android.systemui.util.leak.RotationUtils; 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, mDialog.mWalletViewController.onDeviceLockStateChanged(!unlocked); } if (!mDialog.isShowingControls() && shouldShowControls()) { - mDialog.showControls(mControlsUiController); + mDialog.showControls(mControlsUiControllerOptional.get()); } if (unlocked) { mDialog.hideLockMessage(); @@ -383,7 +385,16 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } }); - 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, } private void seedFavorites() { + if (!mControlsControllerOptional.isPresent()) return; if (mControlsServiceInfos.isEmpty() - || mControlsController.getFavorites().size() > 0) { + || mControlsControllerOptional.get().getFavorites().size() > 0) { return; } @@ -433,7 +445,7 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, return; } - mControlsController.seedFavoritesForComponent( + mControlsControllerOptional.get().seedFavoritesForComponent( preferredComponent, (accepted) -> { Log.i(TAG, "Controls seeded: " + accepted); @@ -636,10 +648,14 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 @@ public class GlobalActionsDialog implements DialogInterface.OnDismissListener, 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 009f5494cefe..cf7fbfa9461e 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.Background 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 @@ class MediaDataManager @Inject constructor( 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 @@ class MediaDataManager @Inject constructor( } 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 fe84d81836e8..387490311644 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.PixelFormat; 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 @@ public class ScreenPinningRequest implements View.OnClickListener, .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 f2d2eb3a0836..33d692f8e1e5 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 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset 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 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset } /** + * 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 @@ public class GlobalScreenshot implements ViewTreeObserver.OnComputeInternalInset 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 f0a81e9b0302..a5bab212e6b7 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { 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 1eadd9ebfd7f..33771449abc9 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.Notification.MessagingStyle.Message; 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.ExpandableNotificationRow 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 @@ public final class NotificationEntry extends ListEntry { 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 @@ public final class NotificationEntry extends ListEntry { 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 @@ public final class NotificationEntry extends ListEntry { boolean allowFgsDismissal, long creationTime ) { - super(requireNonNull(Objects.requireNonNull(sbn).getKey())); + super(requireNonNull(requireNonNull(sbn).getKey())); requireNonNull(ranking); @@ -441,7 +445,7 @@ public final class NotificationEntry extends ListEntry { * 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 @@ public final class NotificationEntry extends ListEntry { } if ((mSbn.getNotification().flags - & Notification.FLAG_FOREGROUND_SERVICE) != 0) { + & FLAG_FOREGROUND_SERVICE) != 0) { return true; } if (mSbn.getNotification().isMediaNotification()) { @@ -942,6 +946,19 @@ public final class NotificationEntry extends ListEntry { 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 f55ce77060a5..033a638bdd73 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 android.view.accessibility.AccessibilityManager; 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 @@ public interface NotificationsModule { VisualStabilityManager visualStabilityManager, Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler, + @Background Handler bgHandler, AccessibilityManager accessibilityManager, HighPriorityProvider highPriorityProvider, INotificationManager notificationManager, @@ -118,6 +120,7 @@ public interface NotificationsModule { 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 e0583be1935d..9217756dca13 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.ApplicationInfo; 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 android.widget.TextView; 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 @@ public class NotificationConversationInfo extends LinearLayout implements 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 @@ public class NotificationConversationInfo extends LinearLayout implements 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 @@ public class NotificationConversationInfo extends LinearLayout implements 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 @@ public class NotificationConversationInfo extends LinearLayout implements mUserContext = userContext; mBubbleMetadata = bubbleMetadata; mBuilderProvider = builderProvider; - + mMainHandler = mainHandler; + mBgHandler = bgHandler; mShortcutManager = shortcutManager; mShortcutInfo = entry.getRanking().getShortcutInfo(); if (mShortcutInfo == null) { @@ -494,11 +499,13 @@ public class NotificationConversationInfo extends LinearLayout implements } 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 3f7c7ca799d6..1caf8f89c822 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.settingslib.notification.ConversationIconFactory; 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 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx 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 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx * 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 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx mVisualStabilityManager = visualStabilityManager; mStatusBarLazy = statusBarLazy; mMainHandler = mainHandler; + mBgHandler = bgHandler; mAccessibilityManager = accessibilityManager; mHighPriorityProvider = highPriorityProvider; mNotificationManager = notificationManager; @@ -463,7 +466,9 @@ public class NotificationGutsManager implements Dumpable, NotificationLifetimeEx 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 bcafd0eeb9a6..a877bc1c4205 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 @@ public class NotificationStackScrollLayout extends ViewGroup implements ScrollAd @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 77850826a5e1..d4add958ad1f 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 class StackStateAnimator { 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/MultiUserSwitch.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java index dcc31075a2ac..67b7e979f62d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/MultiUserSwitch.java @@ -18,10 +18,8 @@ package com.android.systemui.statusbar.phone; import static com.android.systemui.DejankUtils.whitelistIpcs; -import android.app.admin.DevicePolicyManager; import android.content.Context; import android.os.UserManager; -import android.provider.Settings; import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; @@ -97,33 +95,9 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener } public boolean isMultiUserEnabled() { - // Short-circuiting from UserManager. Needs to be extracted because of SystemUI boolean flag - // qs_show_user_switcher_for_single_user - // TODO(b/138661450) Move IPC calls to background - return whitelistIpcs(() -> { - // The default in UserManager is to show the switcher. We want to not show it unless the - // user explicitly requests it in Settings - final boolean userSwitcherEnabled = Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.USER_SWITCHER_ENABLED, 0) != 0; - - if (!userSwitcherEnabled - || !UserManager.supportsMultipleUsers() - || UserManager.isDeviceInDemoMode(mContext) - || mUserManager.hasUserRestriction(UserManager.DISALLOW_USER_SWITCH)) { - return false; - } - - final boolean guestEnabled = !mContext.getSystemService(DevicePolicyManager.class) - .getGuestUserDisabled(null); - return mUserSwitcherController.getSwitchableUserCount() > 1 - // If we cannot add guests even if they are enabled, do not show - || (guestEnabled && !mUserManager.hasUserRestriction( - UserManager.DISALLOW_ADD_USER)) - || mContext.getResources().getBoolean( - R.bool.qs_show_user_switcher_for_single_user); - }); + return whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled( + mContext.getResources().getBoolean(R.bool.qs_show_user_switcher_for_single_user))); } private void registerListener() { @@ -175,7 +149,7 @@ public class MultiUserSwitch extends FrameLayout implements View.OnClickListener private void refreshContentDescription() { String currentUser = null; // TODO(b/138661450) - if (whitelistIpcs(mUserManager::isUserSwitcherEnabled) + if (whitelistIpcs(() -> mUserManager.isUserSwitcherEnabled()) && mUserSwitcherController != null) { currentUser = mUserSwitcherController.getCurrentUserName(mContext); } 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 662c744f8c95..46c873db8a08 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 @@ public class NavigationBarInflaterView extends FrameLayout private boolean mIsVertical; private boolean mAlternativeOrder; - private boolean mUsingCustomLayout; private OverviewProxyService mOverviewProxyService; private int mNavBarMode = NAV_BAR_MODE_3BUTTON; @@ -145,7 +144,6 @@ public class NavigationBarInflaterView extends FrameLayout @Override public void onNavigationModeChanged(int mode) { mNavBarMode = mode; - onLikelyDefaultLayoutChange(); } @Override @@ -154,17 +152,7 @@ public class NavigationBarInflaterView extends FrameLayout 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 06b7d1a34b5a..daefef5e826d 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 android.util.Log; 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 @@ public class NavigationModeController implements Dumpable { 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 @@ public class NavigationModeController implements Dumpable { @Inject public NavigationModeController(Context context, DeviceProvisionedController deviceProvisionedController, + ConfigurationController configurationController, @UiBackground Executor uiBgExecutor) { mContext = context; mCurrentUserContext = context; @@ -105,10 +92,15 @@ public class NavigationModeController implements Dumpable { 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/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 412962cc797a..db00770f0638 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -409,18 +409,6 @@ public class UserSwitcherController implements Dumpable { Log.e(TAG, "Couldn't switch to user, id=" + userId); } - public int getSwitchableUserCount() { - int count = 0; - final int N = mUsers.size(); - for (int i = 0; i < N; ++i) { - UserRecord record = mUsers.get(i); - if (record.info != null && record.info.supportsSwitchToByUser()) { - count++; - } - } - return count; - } - protected void switchToUserId(int id) { try { pauseRefreshUsers(); 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 9b377ca3ec90..c89f6c2597d0 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 @@ public class BubbleControllerTest extends SysuiTestCase { 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 @@ public class BubbleControllerTest extends SysuiTestCase { 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 @@ public class BubbleControllerTest extends SysuiTestCase { 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 @@ public class BubbleControllerTest extends SysuiTestCase { 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 @@ public class BubbleControllerTest extends SysuiTestCase { // 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 @@ public class BubbleControllerTest extends SysuiTestCase { // 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 @@ public class BubbleControllerTest extends SysuiTestCase { @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 class BubbleControllerTest extends SysuiTestCase { 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 @@ public class BubbleControllerTest extends SysuiTestCase { // 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 @@ public class BubbleControllerTest extends SysuiTestCase { 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 @@ public class BubbleControllerTest extends SysuiTestCase { 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 eca78ec38a88..8224c88e6c75 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 @@ public class BubbleDataTest extends SysuiTestCase { 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 b18d67bf2726..ead95ca1665e 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { @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 class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // 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 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { // 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 d49d0219fa54..f46819252fac 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 @@ import org.junit.runner.RunWith 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 7acc93712d9b..ee4884651ee8 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 @@ import org.junit.runner.RunWith @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 ef4580c6b28d..79701ecf70f8 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 @@ import java.io.ByteArrayOutputStream 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 @@ class BubbleXmlHelperTest : SysuiTestCase() { 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 000000000000..7fe682793152 --- /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 663f011183ab..ee1cc7b1ab71 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 @@ class ControlsRequestReceiverTest : SysuiTestCase() { 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 @@ class ControlsRequestReceiverTest : SysuiTestCase() { } ?: 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 487452b0d26a..32546333aac3 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.SysuiTestCase; 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 @@ public class GlobalActionsDialogTest extends SysuiTestCase { @Mock GlobalActionsPanelPlugin.PanelViewController mWalletController; @Mock private Handler mHandler; @Mock private CurrentUserContextTracker mCurrentUserContextTracker; + private ControlsComponent mControlsComponent; private TestableLooper mTestableLooper; @@ -132,6 +134,13 @@ public class GlobalActionsDialogTest extends SysuiTestCase { 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 @@ public class GlobalActionsDialogTest extends SysuiTestCase { 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 4b21ef294db8..0272028e62fc 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.ShortcutInfo; 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 android.widget.TextView; 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 @@ public class NotificationConversationInfoTest extends SysuiTestCase { 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 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon); assertEquals(mIconDrawable, view.getDrawable()); } @@ -275,7 +278,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { 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 @@ public class NotificationConversationInfoTest extends SysuiTestCase { 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 @@ public class NotificationConversationInfoTest extends SysuiTestCase { 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 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name); assertEquals(GONE, nameView.getVisibility()); } @@ -404,7 +415,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { 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 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); final View settingsButton = mNotificationInfo.findViewById(R.id.info); settingsButton.performClick(); @@ -454,7 +469,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -479,7 +496,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - false); + false, + mTestHandler, + mTestHandler); final View settingsButton = mNotificationInfo.findViewById(R.id.info); assertTrue(settingsButton.getVisibility() != View.VISIBLE); } @@ -502,7 +521,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); View view = mNotificationInfo.findViewById(R.id.silence); assertThat(view.isSelected()).isTrue(); } @@ -528,7 +549,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { 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 @@ public class NotificationConversationInfoTest extends SysuiTestCase { 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 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); View fave = mNotificationInfo.findViewById(R.id.priority); fave.performClick(); @@ -627,7 +654,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); mNotificationInfo.findViewById(R.id.default_behavior).performClick(); mTestableLooper.processAllMessages(); @@ -668,7 +697,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); View silence = mNotificationInfo.findViewById(R.id.silence); @@ -710,7 +741,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); View fave = mNotificationInfo.findViewById(R.id.priority); fave.performClick(); @@ -745,7 +778,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); View fave = mNotificationInfo.findViewById(R.id.priority); fave.performClick(); @@ -778,7 +813,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); mNotificationInfo.findViewById(R.id.default_behavior).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -812,7 +849,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); mNotificationInfo.findViewById(R.id.default_behavior).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -846,7 +885,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); mNotificationInfo.findViewById(R.id.default_behavior).performClick(); mNotificationInfo.findViewById(R.id.done).performClick(); @@ -879,7 +920,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); View silence = mNotificationInfo.findViewById(R.id.silence); silence.performClick(); @@ -911,7 +954,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage( anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID)); @@ -934,7 +979,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, mBuilderProvider, - true); + true, + mTestHandler, + mTestHandler); verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage( anyString(), anyInt(), anyString(), any(), eq(CONVERSATION_ID)); @@ -967,7 +1014,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { mIconFactory, mContext, () -> b, - true); + true, + mTestHandler, + mTestHandler); // WHEN user clicks "priority" mNotificationInfo.setSelectedAction(NotificationConversationInfo.ACTION_FAVORITE); @@ -1001,7 +1050,9 @@ public class NotificationConversationInfoTest extends SysuiTestCase { 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 eeb912e7aac8..da7d249cc5ef 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 @@ public class NotificationGutsManagerTest extends SysuiTestCase { 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 103151dcdda5..26e85beba58b 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 @@ import java.util.function.Consumer; */ 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 class AppPredictionPerUserService extends 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 f3c787462d61..a33817c0bf24 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.NetworkCapabilities; 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 @@ public class Vpn { @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 @@ public class Vpn { 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/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 9d56d817440b..77b030f9ed0d 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -85,7 +85,8 @@ public class PreferencesHelper implements RankingConfig { private static final int XML_VERSION = 2; /** What version to check to do the upgrade for bubbles. */ private static final int XML_VERSION_BUBBLES_UPGRADE = 1; - private static final int UNKNOWN_UID = UserHandle.USER_NULL; + @VisibleForTesting + static final int UNKNOWN_UID = UserHandle.USER_NULL; private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":"; @VisibleForTesting @@ -224,7 +225,7 @@ public class PreferencesHelper implements RankingConfig { } boolean skipWarningLogged = false; boolean hasSAWPermission = false; - if (upgradeForBubbles) { + if (upgradeForBubbles && uid != UNKNOWN_UID) { hasSAWPermission = mAppOps.noteOpNoThrow( OP_SYSTEM_ALERT_WINDOW, uid, name, null, "check-notif-bubble") == AppOpsManager.MODE_ALLOWED; diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java index 0fa522cf51de..ee02e3fa8140 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 @@ public class ShortcutHelper { 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 c6d08c36631a..5bbe49088c02 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 @@ package com.android.server.pm; 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.content.PackageMonitor; 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 @@ public class LauncherAppsService extends SystemService { @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 @@ public class LauncherAppsService extends SystemService { 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 @@ public class LauncherAppsService extends SystemService { 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 16426072ae78..9e27f65105eb 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 @@ class ShortcutPackage extends ShortcutPackageItem { // 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 @@ class ShortcutPackage extends ShortcutPackageItem { 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 3732b479c3a3..3ec139763e80 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { @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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { 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 @@ public class ShortcutService extends IShortcutService.Stub { } } 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/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 29428a27e6da..b0e3ecb6d17b 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -3507,7 +3507,7 @@ public class UserManagerService extends IUserManager.Stub { Slog.w(LOG_TAG, "could not start pre-created user " + userId, e); } } else { - dispatchUserAddedIntent(userInfo); + dispatchUserAdded(userInfo); } } finally { @@ -3568,7 +3568,7 @@ public class UserManagerService extends IUserManager.Stub { // Could not read the existing permissions, re-grant them. mPm.onNewUserCreated(preCreatedUser.id); } - dispatchUserAddedIntent(preCreatedUser); + dispatchUserAdded(preCreatedUser); return preCreatedUser; } @@ -3600,7 +3600,7 @@ public class UserManagerService extends IUserManager.Stub { return (now > EPOCH_PLUS_30_YEARS) ? now : 0; } - private void dispatchUserAddedIntent(@NonNull UserInfo userInfo) { + private void dispatchUserAdded(@NonNull UserInfo userInfo) { Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED); addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id); // Also, add the UserHandle for mainline modules which can't use the @hide @@ -3610,6 +3610,15 @@ public class UserManagerService extends IUserManager.Stub { android.Manifest.permission.MANAGE_USERS); MetricsLogger.count(mContext, userInfo.isGuest() ? TRON_GUEST_CREATED : (userInfo.isDemo() ? TRON_DEMO_CREATED : TRON_USER_CREATED), 1); + + if (!userInfo.isProfile()) { + // If the user switch hasn't been explicitly toggled on or off by the user, turn it on. + if (android.provider.Settings.Global.getString(mContext.getContentResolver(), + android.provider.Settings.Global.USER_SWITCHER_ENABLED) == null) { + android.provider.Settings.Global.putInt(mContext.getContentResolver(), + android.provider.Settings.Global.USER_SWITCHER_ENABLED, 1); + } + } } /** 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 29c1243c299d..0b3254f53324 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.Executor; 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { 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[] transports, boolean slicedByFgbg) { + this(stats, transports, slicedByFgbg, /*slicedByTag=*/false, /*slicedByMetered=*/false); + } - NetworkStatsExt(@NonNull NetworkStats stats, int transport, boolean withFgbg) { + 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { } 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 @@ public class StatsPullAtomService extends SystemService { } } + 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 @@ public class StatsPullAtomService extends SystemService { 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 @@ public class StatsPullAtomService extends SystemService { ); } + 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 dc3fa2a048f6..17378285276f 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 @@ public class ConversationInfo { 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 @@ public class ConversationInfo { 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 bbb0215788fb..63b716206313 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 @@ public class DataManager { 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 @@ public class DataManager { // 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 @@ public class DataManager { 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 70d6cf81c3b0..c5d94875b684 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 @@ public final class ConversationInfoTest { .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 @@ public final class ConversationInfoTest { 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 @@ public final class ConversationInfoTest { 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 1a2032ac15d0..b2f7abbf84df 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 @@ public final class DataManagerTest { 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 @@ public final class DataManagerTest { 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 @@ public final class DataManagerTest { 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 @@ public final class DataManagerTest { 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 @@ public final class DataManagerTest { 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 @@ public final class DataManagerTest { 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 @@ public final class DataManagerTest { 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 @@ public final class DataManagerTest { 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 @@ public final class DataManagerTest { 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 @@ public final class DataManagerTest { 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 db02524e6fab..90989b9eda84 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 @@ import java.util.function.BiConsumer; @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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { 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 621966535306..6a2b8e0da2d2 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.times; 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { }); 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { }); 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { 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 @@ public class ShortcutManagerTest11 extends BaseShortcutManagerTest { 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 2bea491949de..cf636823d5f7 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // 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 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // 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/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 078c21e04512..1d6f8233b7b4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -31,6 +31,7 @@ import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED; import static com.android.server.notification.PreferencesHelper.DEFAULT_BUBBLE_PREFERENCE; import static com.android.server.notification.PreferencesHelper.NOTIFICATION_CHANNEL_COUNT_LIMIT; +import static com.android.server.notification.PreferencesHelper.UNKNOWN_UID; import static com.google.common.truth.Truth.assertThat; @@ -2511,6 +2512,26 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test + public void testBubblePrefence_noSAWCheckForUnknownUid() throws Exception { + final String xml = "<ranking version=\"1\">\n" + + "<package name=\"" + PKG_O + "\" uid=\"" + UNKNOWN_UID + "\">\n" + + "<channel id=\"someId\" name=\"hi\"" + + " importance=\"3\"/>" + + "</package>" + + "</ranking>"; + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(new BufferedInputStream(new ByteArrayInputStream(xml.getBytes())), + null); + parser.nextTag(); + mHelper.readXml(parser, false, UserHandle.USER_ALL); + + assertEquals(DEFAULT_BUBBLE_PREFERENCE, mHelper.getBubblePreference(PKG_O, UID_O)); + assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O)); + verify(mAppOpsManager, never()).noteOpNoThrow(eq(OP_SYSTEM_ALERT_WINDOW), anyInt(), + anyString(), eq(null), anyString()); + } + + @Test public void testBubblePreference_xml() throws Exception { mHelper.setBubblesAllowed(PKG_O, UID_O, BUBBLE_PREFERENCE_NONE); assertEquals(mHelper.getBubblePreference(PKG_O, UID_O), BUBBLE_PREFERENCE_NONE); diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java index 43db1d9ce8a4..f6c14e67306b 100644 --- a/telephony/java/android/telephony/ims/ImsMmTelManager.java +++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java @@ -518,9 +518,6 @@ public class ImsMmTelManager implements RegistrationManager { * @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 @@ public class ImsMmTelManager implements RegistrationManager { 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 5c7c68b57ba1..53d36389a52a 100644 --- a/tests/BlobStoreTestUtils/Android.bp +++ b/tests/BlobStoreTestUtils/Android.bp @@ -18,6 +18,7 @@ java_library { 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 371375c0d932..4a0ca664049a 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 @@ public class DummyBlobData { private final File mFile; private final long mFileSize; private final CharSequence mLabel; + private final long mExpiryDurationMs; byte[] mFileDigest; long mExpiryTimeMs; @@ -51,6 +52,7 @@ public class DummyBlobData { 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 @@ public class DummyBlobData { 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 @@ public class DummyBlobData { 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 @@ public class DummyBlobData { 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 7cf58e1682bf..b9bd661dfd67 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 @@ package com.android.utils.blob; 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.content.res.Resources; 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 @@ public class Utils { 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 ceca6f028866..e5daa71c30ea 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.Test; 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 @@ import java.util.Arrays; 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 @@ public class VpnProfileTest { 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 @@ public class VpnProfileTest { @Test public void testParcelUnparcel() { - assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 22); + assertParcelSane(getSampleIkev2Profile(DUMMY_PROFILE_KEY), 23); } @Test @@ -159,14 +165,41 @@ public class VpnProfileTest { 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 |