diff options
Diffstat (limited to 'services')
70 files changed, 1842 insertions, 723 deletions
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/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 7c61ba2e7adb..1e9a9d85e06a 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -3102,30 +3102,24 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void notifyDataStallSuspected(DataStallReportParcelable p, int netId) { + log("Data stall detected with methods: " + p.detectionMethod); + final PersistableBundle extras = new PersistableBundle(); - switch (p.detectionMethod) { - case DETECTION_METHOD_DNS_EVENTS: - extras.putInt(KEY_DNS_CONSECUTIVE_TIMEOUTS, p.dnsConsecutiveTimeouts); - break; - case DETECTION_METHOD_TCP_METRICS: - extras.putInt(KEY_TCP_PACKET_FAIL_RATE, p.tcpPacketFailRate); - extras.putInt(KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS, - p.tcpMetricsCollectionPeriodMillis); - break; - default: - // TODO(b/156294356): update for new data stall detection methods - log("Unknown data stall detection method, ignoring: " + p.detectionMethod); - return; + int detectionMethod = 0; + if (hasDataStallDetectionMethod(p, DETECTION_METHOD_DNS_EVENTS)) { + extras.putInt(KEY_DNS_CONSECUTIVE_TIMEOUTS, p.dnsConsecutiveTimeouts); + detectionMethod |= DETECTION_METHOD_DNS_EVENTS; + } + if (hasDataStallDetectionMethod(p, DETECTION_METHOD_TCP_METRICS)) { + extras.putInt(KEY_TCP_PACKET_FAIL_RATE, p.tcpPacketFailRate); + extras.putInt(KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS, + p.tcpMetricsCollectionPeriodMillis); + detectionMethod |= DETECTION_METHOD_TCP_METRICS; } - notifyDataStallSuspected(p.detectionMethod, netId, p.timestampMillis, extras); - } - - private void notifyDataStallSuspected(int detectionMethod, int netId, long timestampMillis, - @NonNull PersistableBundle extras) { final Message msg = mConnectivityDiagnosticsHandler.obtainMessage( ConnectivityDiagnosticsHandler.EVENT_DATA_STALL_SUSPECTED, detectionMethod, netId, - timestampMillis); + p.timestampMillis); msg.setData(new Bundle(extras)); // NetworkStateTrackerHandler currently doesn't take any actions based on data @@ -3134,6 +3128,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mConnectivityDiagnosticsHandler.sendMessage(msg); } + private boolean hasDataStallDetectionMethod(DataStallReportParcelable p, int detectionMethod) { + return (p.detectionMethod & detectionMethod) != 0; + } + private boolean networkRequiresPrivateDnsValidation(NetworkAgentInfo nai) { return isPrivateDnsValidationRequired(nai.networkCapabilities); } @@ -8189,6 +8187,19 @@ public class ConnectivityService extends IConnectivityManager.Stub + "creators"); } - notifyDataStallSuspected(detectionMethod, network.netId, timestampMillis, extras); + final DataStallReportParcelable p = new DataStallReportParcelable(); + p.timestampMillis = timestampMillis; + p.detectionMethod = detectionMethod; + + if (hasDataStallDetectionMethod(p, DETECTION_METHOD_DNS_EVENTS)) { + p.dnsConsecutiveTimeouts = extras.getInt(KEY_DNS_CONSECUTIVE_TIMEOUTS); + } + if (hasDataStallDetectionMethod(p, DETECTION_METHOD_TCP_METRICS)) { + p.tcpPacketFailRate = extras.getInt(KEY_TCP_PACKET_FAIL_RATE); + p.tcpMetricsCollectionPeriodMillis = extras.getInt( + KEY_TCP_METRICS_COLLECTION_PERIOD_MILLIS); + } + + notifyDataStallSuspected(p, network.netId); } } diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java index e77458cc955a..e303f0d00214 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -177,9 +177,6 @@ public class PackageWatchdog { // 0 if no prune is scheduled. @GuardedBy("mLock") private long mUptimeAtLastStateSync; - // If true, sync explicit health check packages with the ExplicitHealthCheckController. - @GuardedBy("mLock") - private boolean mSyncRequired = false; @FunctionalInterface @VisibleForTesting @@ -255,7 +252,6 @@ public class PackageWatchdog { */ public void registerHealthObserver(PackageHealthObserver observer) { synchronized (mLock) { - mSyncRequired = true; ObserverInternal internalObserver = mAllObservers.get(observer.getName()); if (internalObserver != null) { internalObserver.registeredObserver = observer; @@ -642,7 +638,7 @@ public class PackageWatchdog { synchronized (mLock) { if (mIsPackagesReady) { Set<String> packages = getPackagesPendingHealthChecksLocked(); - if (!packages.equals(mRequestedHealthCheckPackages) || mSyncRequired) { + if (!packages.equals(mRequestedHealthCheckPackages) || packages.isEmpty()) { syncRequired = true; mRequestedHealthCheckPackages = packages; } @@ -654,7 +650,6 @@ public class PackageWatchdog { Slog.i(TAG, "Syncing health check requests for packages: " + mRequestedHealthCheckPackages); mHealthCheckController.syncRequests(mRequestedHealthCheckPackages); - mSyncRequired = false; } } diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java index e5c05408f628..45d53a14d1e9 100644 --- a/services/core/java/com/android/server/SystemService.java +++ b/services/core/java/com/android/server/SystemService.java @@ -68,8 +68,7 @@ import java.util.List; public abstract class SystemService { /** @hide */ - // TODO(b/133242016) STOPSHIP: change to false before R ships - protected static final boolean DEBUG_USER = true; + protected static final boolean DEBUG_USER = false; /* * The earliest boot phase the system send to system services on boot. diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index cca604655f3d..ec12aebc37f6 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -4914,8 +4914,13 @@ public final class ActiveServices { // TODO: remove this toast after feature development is done void showWhileInUseDebugToastLocked(int uid, int op, int mode) { - for (int i = mAm.mProcessList.mLruProcesses.size() - 1; i >= 0; i--) { - ProcessRecord pr = mAm.mProcessList.mLruProcesses.get(i); + final UidRecord uidRec = mAm.mProcessList.getUidRecordLocked(uid); + if (uidRec == null) { + return; + } + + for (int i = uidRec.procRecords.size() - 1; i >= 0; i--) { + ProcessRecord pr = uidRec.procRecords.valueAt(i); if (pr.uid != uid) { continue; } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 671733b7cb7a..7ef527cb7d84 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -11112,6 +11112,14 @@ public class ActivityManagerService extends IActivityManager.Stub } pw.print(" UID "); UserHandle.formatUid(pw, uidRec.uid); pw.print(": "); pw.println(uidRec); + pw.print(" curProcState="); pw.print(uidRec.mCurProcState); + pw.print(" curCapability="); + ActivityManager.printCapabilitiesFull(pw, uidRec.curCapability); + pw.println(); + for (int j = uidRec.procRecords.size() - 1; j >= 0; j--) { + pw.print(" proc="); + pw.println(uidRec.procRecords.valueAt(j)); + } } return printed; } diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index f7a158a885c6..a81590c8c022 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -324,8 +324,21 @@ public final class OomAdjuster { boolean success = applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime()); if (uidRec != null) { - updateAppUidRecLocked(app); - // If this proc state is changed, need to update its uid record here + // After uidRec.reset() above, for UidRecord that has multiple processes (ProcessRecord) + // , We need to apply all ProcessRecord into UidRecord. + final ArraySet<ProcessRecord> procRecords = app.uidRecord.procRecords; + for (int i = procRecords.size() - 1; i >= 0; i--) { + final ProcessRecord pr = procRecords.valueAt(i); + if (!pr.killedByAm && pr.thread != null) { + if (pr.isolated && pr.numberOfRunningServices() <= 0 + && pr.isolatedEntryPoint == null) { + // No op. + } else { + // Keeping this process, update its uid. + updateAppUidRecLocked(pr); + } + } + } if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT && (uidRec.setProcState != uidRec.getCurProcState() || uidRec.setCapability != uidRec.curCapability diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 108fb7dff2cd..9f2a77c05d2b 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2808,6 +2808,7 @@ public final class ProcessList { uidRec.curCapability); } proc.uidRecord = uidRec; + uidRec.procRecords.add(proc); // Reset render thread tid if it was already set, so new process can set it again. proc.renderThreadTid = 0; @@ -2901,6 +2902,7 @@ public final class ProcessList { } if (old != null && old.uidRecord != null) { old.uidRecord.numProcs--; + old.uidRecord.procRecords.remove(old); if (old.uidRecord.numProcs == 0) { // No more processes using this uid, tell clients it is gone. if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, diff --git a/services/core/java/com/android/server/am/UidRecord.java b/services/core/java/com/android/server/am/UidRecord.java index acf8b2e84821..c84ccb298bef 100644 --- a/services/core/java/com/android/server/am/UidRecord.java +++ b/services/core/java/com/android/server/am/UidRecord.java @@ -21,6 +21,7 @@ import android.app.ActivityManager; import android.content.pm.PackageManager; import android.os.SystemClock; import android.os.UserHandle; +import android.util.ArraySet; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.util.proto.ProtoUtils; @@ -32,7 +33,7 @@ import com.android.internal.annotations.GuardedBy; */ public final class UidRecord { final int uid; - private int mCurProcState; + int mCurProcState; int setProcState = ActivityManager.PROCESS_STATE_NONEXISTENT; int curCapability; int setCapability; @@ -44,6 +45,7 @@ public final class UidRecord { boolean idle; boolean setIdle; int numProcs; + ArraySet<ProcessRecord> procRecords = new ArraySet<>(); /** * Sequence number associated with the {@link #mCurProcState}. This is incremented using diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java index f5d6e5ac46e5..08d266478f4b 100644 --- a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java +++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java @@ -89,7 +89,7 @@ public class OverrideValidatorImpl extends IOverrideValidator.Stub { return new OverrideAllowedState(DISABLED_NON_TARGET_SDK, appTargetSdk, minTargetSdk); } // Only allow to opt-in for a targetSdk gated change. - if (disabled || applicationInfo.targetSdkVersion < minTargetSdk) { + if (disabled || appTargetSdk <= minTargetSdk) { return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk); } return new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, appTargetSdk, minTargetSdk); diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java index 34d0bed9a802..3091a7178377 100644 --- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java +++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java @@ -198,6 +198,9 @@ public class Nat464Xlat extends BaseNetworkObserver { if (mPrefixDiscoveryRunning && !isPrefixDiscoveryNeeded()) { stopPrefixDiscovery(); } + if (!mPrefixDiscoveryRunning) { + setPrefix64(mNat64PrefixInUse); + } } /** @@ -221,6 +224,10 @@ public class Nat464Xlat extends BaseNetworkObserver { mIface = null; mBaseIface = null; + if (!mPrefixDiscoveryRunning) { + setPrefix64(null); + } + if (isPrefixDiscoveryNeeded()) { if (!mPrefixDiscoveryRunning) { startPrefixDiscovery(); @@ -308,6 +315,16 @@ public class Nat464Xlat extends BaseNetworkObserver { return requiresClat(mNetwork) && mNat64PrefixFromRa == null; } + private void setPrefix64(IpPrefix prefix) { + final String prefixString = (prefix != null) ? prefix.toString() : ""; + try { + mDnsResolver.setPrefix64(getNetId(), prefixString); + } catch (RemoteException | ServiceSpecificException e) { + Slog.e(TAG, "Error setting NAT64 prefix on netId " + getNetId() + " to " + + prefix + ": " + e); + } + } + private void maybeHandleNat64PrefixChange() { final IpPrefix newPrefix = selectNat64Prefix(); if (!Objects.equals(mNat64PrefixInUse, newPrefix)) { 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/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java index b9cd43d0803d..787f7f398fcb 100644 --- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java +++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java @@ -28,7 +28,6 @@ import android.util.Pair; import android.util.Slog; import android.util.Spline; -import com.android.internal.BrightnessSynchronizer; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.server.display.utils.Plog; @@ -347,10 +346,11 @@ public abstract class BrightnessMappingStrategy { } } + // Normalize entire brightness range to 0 - 1. protected static float normalizeAbsoluteBrightness(int brightness) { - return BrightnessSynchronizer.brightnessIntToFloat(brightness, - PowerManager.BRIGHTNESS_OFF + 1, PowerManager.BRIGHTNESS_ON, - PowerManager.BRIGHTNESS_MIN, PowerManager.BRIGHTNESS_MAX); + brightness = MathUtils.constrain(brightness, + PowerManager.BRIGHTNESS_OFF, PowerManager.BRIGHTNESS_ON); + return (float) brightness / PowerManager.BRIGHTNESS_ON; } private Pair<float[], float[]> insertControlPoint( diff --git a/services/core/java/com/android/server/notification/BubbleExtractor.java b/services/core/java/com/android/server/notification/BubbleExtractor.java index d7d413c2ffb0..0fa339f65cdb 100644 --- a/services/core/java/com/android/server/notification/BubbleExtractor.java +++ b/services/core/java/com/android/server/notification/BubbleExtractor.java @@ -75,9 +75,19 @@ public class BubbleExtractor implements NotificationSignalExtractor { mConfig.getBubblePreference( record.getSbn().getPackageName(), record.getSbn().getUid()); NotificationChannel recordChannel = record.getChannel(); + boolean canPresentAsBubble = canPresentAsBubble(record) + && !mActivityManager.isLowRamDevice() + && record.isConversation() + && (record.getNotification().flags & FLAG_FOREGROUND_SERVICE) == 0; - if (!mConfig.bubblesEnabled() || bubblePreference == BUBBLE_PREFERENCE_NONE) { + if (!mConfig.bubblesEnabled() + || bubblePreference == BUBBLE_PREFERENCE_NONE + || !canPresentAsBubble) { record.setAllowBubble(false); + if (!canPresentAsBubble) { + // clear out bubble metadata since it can't be used + record.getNotification().setBubbleMetadata(null); + } } else if (recordChannel == null) { // the app is allowed but there's no channel to check record.setAllowBubble(true); @@ -86,14 +96,15 @@ public class BubbleExtractor implements NotificationSignalExtractor { } else if (bubblePreference == BUBBLE_PREFERENCE_SELECTED) { record.setAllowBubble(recordChannel.canBubble()); } + if (DBG) { + Slog.d(TAG, "record: " + record.getKey() + + " appPref: " + bubblePreference + + " canBubble: " + record.canBubble() + + " canPresentAsBubble: " + canPresentAsBubble + + " flagRemoved: " + record.isFlagBubbleRemoved()); + } - final boolean fulfillsPolicy = record.canBubble() - && record.isConversation() - && !mActivityManager.isLowRamDevice() - && (record.getNotification().flags & FLAG_FOREGROUND_SERVICE) == 0; - final boolean applyFlag = fulfillsPolicy - && canPresentAsBubble(record) - && !record.isFlagBubbleRemoved(); + final boolean applyFlag = record.canBubble() && !record.isFlagBubbleRemoved(); if (applyFlag) { record.getNotification().flags |= FLAG_BUBBLE; } else { 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 e79d33fa5f7a..ee02e3fa8140 100644 --- a/services/core/java/com/android/server/notification/ShortcutHelper.java +++ b/services/core/java/com/android/server/notification/ShortcutHelper.java @@ -28,6 +28,7 @@ import android.content.pm.ShortcutServiceInternal; import android.os.Binder; import android.os.Handler; import android.os.UserHandle; +import android.text.TextUtils; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; @@ -111,6 +112,15 @@ public class ShortcutHelper { } if (!foundShortcut) { bubbleKeysToRemove.add(shortcutBubbles.get(shortcutId)); + shortcutBubbles.remove(shortcutId); + if (shortcutBubbles.isEmpty()) { + mActiveShortcutBubbles.remove(packageName); + if (mLauncherAppsCallbackRegistered + && mActiveShortcutBubbles.isEmpty()) { + mLauncherAppsService.unregisterCallback(mLauncherAppsCallback); + mLauncherAppsCallbackRegistered = false; + } + } } } } @@ -198,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); } } @@ -209,15 +219,16 @@ public class ShortcutHelper { * @param removedNotification true if this notification is being removed * @param handler handler to register the callback with */ - void maybeListenForShortcutChangesForBubbles(NotificationRecord r, boolean removedNotification, + void maybeListenForShortcutChangesForBubbles(NotificationRecord r, + boolean removedNotification, Handler handler) { final String shortcutId = r.getNotification().getBubbleMetadata() != null ? r.getNotification().getBubbleMetadata().getShortcutId() : null; - if (shortcutId == null) { - return; - } - if (r.getNotification().isBubbleNotification() && !removedNotification) { + if (!removedNotification + && !TextUtils.isEmpty(shortcutId) + && r.getShortcutInfo() != null + && r.getShortcutInfo().getId().equals(shortcutId)) { // Must track shortcut based bubbles in case the shortcut is removed HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get( r.getSbn().getPackageName()); @@ -235,10 +246,21 @@ public class ShortcutHelper { HashMap<String, String> packageBubbles = mActiveShortcutBubbles.get( r.getSbn().getPackageName()); if (packageBubbles != null) { - packageBubbles.remove(shortcutId); - } - if (packageBubbles != null && packageBubbles.isEmpty()) { - mActiveShortcutBubbles.remove(r.getSbn().getPackageName()); + if (!TextUtils.isEmpty(shortcutId)) { + packageBubbles.remove(shortcutId); + } else { + // Check if there was a matching entry + for (String pkgShortcutId : packageBubbles.keySet()) { + String entryKey = packageBubbles.get(pkgShortcutId); + if (r.getKey().equals(entryKey)) { + // No longer has shortcut id so remove it + packageBubbles.remove(pkgShortcutId); + } + } + } + if (packageBubbles.isEmpty()) { + mActiveShortcutBubbles.remove(r.getSbn().getPackageName()); + } } if (mLauncherAppsCallbackRegistered && mActiveShortcutBubbles.isEmpty()) { mLauncherAppsService.unregisterCallback(mLauncherAppsCallback); diff --git a/services/core/java/com/android/server/om/IdmapDaemon.java b/services/core/java/com/android/server/om/IdmapDaemon.java index 910ed44df0f8..19fa9204935d 100644 --- a/services/core/java/com/android/server/om/IdmapDaemon.java +++ b/services/core/java/com/android/server/om/IdmapDaemon.java @@ -30,6 +30,7 @@ import android.util.Slog; import com.android.server.FgThread; +import java.io.File; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; @@ -124,9 +125,13 @@ class IdmapDaemon { } } - String getIdmapPath(String overlayPath, int userId) throws TimeoutException, RemoteException { + boolean idmapExists(String overlayPath, int userId) { try (Connection c = connect()) { - return mService.getIdmapPath(overlayPath, userId); + return new File(mService.getIdmapPath(overlayPath, userId)).isFile(); + } catch (Exception e) { + Slog.wtf(TAG, "failed to check if idmap exists for " + overlayPath + ": " + + e.getMessage()); + return false; } } diff --git a/services/core/java/com/android/server/om/IdmapManager.java b/services/core/java/com/android/server/om/IdmapManager.java index 90c85ada9def..735d66983520 100644 --- a/services/core/java/com/android/server/om/IdmapManager.java +++ b/services/core/java/com/android/server/om/IdmapManager.java @@ -27,10 +27,10 @@ import android.content.pm.PackageInfo; import android.os.Build.VERSION_CODES; import android.os.OverlayablePolicy; import android.os.SystemProperties; -import android.os.UserHandle; import android.util.Slog; -import java.io.File; +import com.android.internal.util.ArrayUtils; + import java.io.IOException; /** @@ -55,12 +55,16 @@ class IdmapManager { VENDOR_IS_Q_OR_LATER = isQOrLater; } - private final OverlayableInfoCallback mOverlayableCallback; private final IdmapDaemon mIdmapDaemon; + private final OverlayableInfoCallback mOverlayableCallback; + private final String mOverlayableConfigurator; + private final String[] mOverlayableConfiguratorTargets; - IdmapManager(final OverlayableInfoCallback verifyCallback) { + IdmapManager(final IdmapDaemon idmapDaemon, final OverlayableInfoCallback verifyCallback) { mOverlayableCallback = verifyCallback; - mIdmapDaemon = IdmapDaemon.getInstance(); + mIdmapDaemon = idmapDaemon; + mOverlayableConfigurator = verifyCallback.getOverlayableConfigurator(); + mOverlayableConfiguratorTargets = verifyCallback.getOverlayableConfiguratorTargets() ; } /** @@ -103,23 +107,11 @@ class IdmapManager { } boolean idmapExists(@NonNull final OverlayInfo oi) { - return new File(getIdmapPath(oi.baseCodePath, oi.userId)).isFile(); + return mIdmapDaemon.idmapExists(oi.baseCodePath, oi.userId); } boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) { - return new File(getIdmapPath(overlayPackage.applicationInfo.getBaseCodePath(), userId)) - .isFile(); - } - - private @NonNull String getIdmapPath(@NonNull final String overlayPackagePath, - final int userId) { - try { - return mIdmapDaemon.getIdmapPath(overlayPackagePath, userId); - } catch (Exception e) { - Slog.w(TAG, "failed to get idmap path for " + overlayPackagePath + ": " - + e.getMessage()); - return ""; - } + return mIdmapDaemon.idmapExists(overlayPackage.applicationInfo.getBaseCodePath(), userId); } /** @@ -198,9 +190,17 @@ class IdmapManager { String targetOverlayableName = overlayPackage.targetOverlayableName; if (targetOverlayableName != null) { try { + if (!mOverlayableConfigurator.isEmpty() + && ArrayUtils.contains(mOverlayableConfiguratorTargets, + targetPackage.packageName) + && mOverlayableCallback.signaturesMatching(mOverlayableConfigurator, + overlayPackage.packageName, userId)) { + return true; + } + OverlayableInfo overlayableInfo = mOverlayableCallback.getOverlayableForTarget( targetPackage.packageName, targetOverlayableName, userId); - if (overlayableInfo != null) { + if (overlayableInfo != null && overlayableInfo.actor != null) { String actorPackageName = OverlayActorEnforcer.getPackageNameForActor( overlayableInfo.actor, mOverlayableCallback.getNamedActors()).first; if (mOverlayableCallback.signaturesMatching(actorPackageName, diff --git a/services/core/java/com/android/server/om/OverlayActorEnforcer.java b/services/core/java/com/android/server/om/OverlayActorEnforcer.java index ef6655dd224e..2bc34998785b 100644 --- a/services/core/java/com/android/server/om/OverlayActorEnforcer.java +++ b/services/core/java/com/android/server/om/OverlayActorEnforcer.java @@ -50,8 +50,8 @@ public class OverlayActorEnforcer { /** * @return nullable actor result with {@link ActorState} failure status */ - static Pair<String, ActorState> getPackageNameForActor(String actorUriString, - Map<String, Map<String, String>> namedActors) { + static Pair<String, ActorState> getPackageNameForActor(@NonNull String actorUriString, + @NonNull Map<String, Map<String, String>> namedActors) { Uri actorUri = Uri.parse(actorUriString); String actorScheme = actorUri.getScheme(); diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java index 6a8e465529d7..086ab8183254 100644 --- a/services/core/java/com/android/server/om/OverlayManagerService.java +++ b/services/core/java/com/android/server/om/OverlayManagerService.java @@ -45,6 +45,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManagerInternal; import android.content.pm.UserInfo; import android.content.res.ApkAssets; +import android.content.res.Resources; import android.net.Uri; import android.os.Binder; import android.os.Environment; @@ -62,6 +63,7 @@ import android.util.AtomicFile; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.R; import com.android.internal.content.om.OverlayConfig; import com.android.server.FgThread; import com.android.server.IoThread; @@ -246,7 +248,7 @@ public final class OverlayManagerService extends SystemService { new File(Environment.getDataSystemDirectory(), "overlays.xml"), "overlays"); mPackageManager = new PackageManagerHelperImpl(context); mUserManager = UserManagerService.getInstance(); - IdmapManager im = new IdmapManager(mPackageManager); + IdmapManager im = new IdmapManager(IdmapDaemon.getInstance(), mPackageManager); mSettings = new OverlayManagerSettings(); mImpl = new OverlayManagerServiceImpl(mPackageManager, im, mSettings, OverlayConfig.getSystemInstance(), getDefaultOverlayPackages(), @@ -1119,6 +1121,17 @@ public final class OverlayManagerService extends SystemService { } @Override + public String getOverlayableConfigurator() { + return Resources.getSystem().getString(R.string.config_overlayableConfigurator); + } + + @Override + public String[] getOverlayableConfiguratorTargets() { + return Resources.getSystem().getStringArray( + R.array.config_overlayableConfiguratorTargets); + } + + @Override public List<PackageInfo> getOverlayPackages(final int userId) { final List<PackageInfo> overlays = mPackageManagerInternal.getOverlayPackages(userId); for (final PackageInfo info : overlays) { diff --git a/services/core/java/com/android/server/om/OverlayableInfoCallback.java b/services/core/java/com/android/server/om/OverlayableInfoCallback.java index 5066ecdd6316..41c341adf1bc 100644 --- a/services/core/java/com/android/server/om/OverlayableInfoCallback.java +++ b/services/core/java/com/android/server/om/OverlayableInfoCallback.java @@ -80,4 +80,24 @@ public interface OverlayableInfoCallback { * in the system returns {@link PackageManager#SIGNATURE_MATCH} */ boolean signaturesMatching(@NonNull String pkgName1, @NonNull String pkgName2, int userId); + + /** + * Retrieves the package name that is recognized as an actor for the packages specified by + * {@link #getOverlayableConfiguratorTargets()}. + */ + @NonNull + default String getOverlayableConfigurator() { + return ""; + } + + /** + * Retrieves the target packages that recognize the {@link #getOverlayableConfigurator} as an + * actor for its overlayable declarations. Overlays targeting one of the specified targets that + * are signed with the same signature as the overlayable configurator will be granted the + * "actor" policy. + */ + @NonNull + default String[] getOverlayableConfiguratorTargets() { + return new String[0]; + } } diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 830388d8f2ac..4b6ee71803a7 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -446,11 +446,6 @@ public class AppsFilter { } } - if (!newPkgSetting.pkg.getProtectedBroadcasts().isEmpty()) { - mProtectedBroadcasts.addAll(newPkgSetting.pkg.getProtectedBroadcasts()); - recomputeComponentVisibility(existingSettings, newPkgSetting.pkg.getPackageName()); - } - Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage"); try { final AndroidPackage newPkg = newPkgSetting.pkg; @@ -459,6 +454,11 @@ public class AppsFilter { return; } + if (!newPkg.getProtectedBroadcasts().isEmpty()) { + mProtectedBroadcasts.addAll(newPkg.getProtectedBroadcasts()); + recomputeComponentVisibility(existingSettings, newPkg.getPackageName()); + } + final boolean newIsForceQueryable = mForceQueryable.contains(newPkgSetting.appId) /* shared user that is already force queryable */ 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/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index f8278de1531d..7ab05c4f762c 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -105,8 +105,10 @@ import android.os.RemoteException; import android.os.RevocableFileDescriptor; import android.os.SystemProperties; import android.os.UserHandle; +import android.os.incremental.IStorageHealthListener; import android.os.incremental.IncrementalFileStorages; import android.os.incremental.IncrementalManager; +import android.os.incremental.StorageHealthCheckParams; import android.os.storage.StorageManager; import android.provider.Settings.Secure; import android.stats.devicepolicy.DevicePolicyEnums; @@ -231,6 +233,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String SYSTEM_DATA_LOADER_PACKAGE = "android"; + private static final int INCREMENTAL_STORAGE_BLOCKED_TIMEOUT_MS = 2000; + private static final int INCREMENTAL_STORAGE_UNHEALTHY_TIMEOUT_MS = 7000; + private static final int INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS = 60000; + // TODO: enforce INSTALL_ALLOW_TEST // TODO: enforce INSTALL_ALLOW_DOWNGRADE @@ -1568,7 +1574,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { dispatchSessionFinished(error, detailMessage, null); } - private void onDataLoaderUnrecoverable() { + private void onStorageUnhealthy() { if (TextUtils.isEmpty(mPackageName)) { // The package has not been installed. return; @@ -2745,7 +2751,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final DataLoaderParams params = this.params.dataLoaderParams; final boolean manualStartAndDestroy = !isIncrementalInstallation(); - final IDataLoaderStatusListener listener = new IDataLoaderStatusListener.Stub() { + final IDataLoaderStatusListener statusListener = new IDataLoaderStatusListener.Stub() { @Override public void onStatusChanged(int dataLoaderId, int status) { switch (status) { @@ -2757,7 +2763,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (mDestroyed || mDataLoaderFinished) { switch (status) { case IDataLoaderStatusListener.DATA_LOADER_UNRECOVERABLE: - onDataLoaderUnrecoverable(); + onStorageUnhealthy(); return; } return; @@ -2840,9 +2846,49 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { }; if (!manualStartAndDestroy) { + final StorageHealthCheckParams healthCheckParams = new StorageHealthCheckParams(); + healthCheckParams.blockedTimeoutMs = INCREMENTAL_STORAGE_BLOCKED_TIMEOUT_MS; + healthCheckParams.unhealthyTimeoutMs = INCREMENTAL_STORAGE_UNHEALTHY_TIMEOUT_MS; + healthCheckParams.unhealthyMonitoringMs = INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS; + + final boolean systemDataLoader = + params.getComponentName().getPackageName() == SYSTEM_DATA_LOADER_PACKAGE; + final IStorageHealthListener healthListener = new IStorageHealthListener.Stub() { + @Override + public void onHealthStatus(int storageId, int status) { + if (mDestroyed || mDataLoaderFinished) { + // App's installed. + switch (status) { + case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY: + onStorageUnhealthy(); + return; + } + return; + } + + switch (status) { + case IStorageHealthListener.HEALTH_STATUS_OK: + break; + case IStorageHealthListener.HEALTH_STATUS_READS_PENDING: + case IStorageHealthListener.HEALTH_STATUS_BLOCKED: + if (systemDataLoader) { + // It's OK for ADB data loader to wait for pages. + break; + } + // fallthrough + case IStorageHealthListener.HEALTH_STATUS_UNHEALTHY: + // Even ADB installation can't wait for missing pages for too long. + mDataLoaderFinished = true; + dispatchSessionVerificationFailure(INSTALL_FAILED_MEDIA_UNAVAILABLE, + "Image is missing pages required for installation."); + break; + } + } + }; + try { mIncrementalFileStorages = IncrementalFileStorages.initialize(mContext, stageDir, - params, listener, addedFiles); + params, statusListener, healthCheckParams, healthListener, addedFiles); return false; } catch (IOException e) { throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, e.getMessage(), @@ -2850,8 +2896,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } - if (!dataLoaderManager.bindToDataLoader( - sessionId, params.getData(), listener)) { + if (!dataLoaderManager.bindToDataLoader(sessionId, params.getData(), statusListener)) { throw new PackageManagerException(INSTALL_FAILED_MEDIA_UNAVAILABLE, "Failed to initialize data loader"); } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 395e835b6a2a..aa7a1ad2b47a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -369,6 +369,7 @@ import com.android.server.pm.permission.PermissionsState; import com.android.server.policy.PermissionPolicyInternal; import com.android.server.security.VerityUtils; import com.android.server.storage.DeviceStorageMonitorInternal; +import com.android.server.uri.UriGrantsManagerInternal; import com.android.server.utils.TimingsTraceAndSlog; import com.android.server.wm.ActivityTaskManagerInternal; @@ -4406,6 +4407,11 @@ public class PackageManagerService extends IPackageManager.Stub if (getInstantAppPackageName(callingUid) != null) { throw new SecurityException("Instant applications don't have access to this method"); } + if (!mUserManager.exists(userId)) { + throw new SecurityException("User doesn't exist"); + } + mPermissionManager.enforceCrossUserPermission( + callingUid, userId, false, false, "checkPackageStartable"); final boolean userKeyUnlocked = StorageManager.isUserKeyUnlocked(userId); synchronized (mLock) { final PackageSetting ps = mSettings.mPackages.get(packageName); @@ -5778,9 +5784,15 @@ public class PackageManagerService extends IPackageManager.Stub @Override public ChangedPackages getChangedPackages(int sequenceNumber, int userId) { - if (getInstantAppPackageName(Binder.getCallingUid()) != null) { + final int callingUid = Binder.getCallingUid(); + if (getInstantAppPackageName(callingUid) != null) { + return null; + } + if (!mUserManager.exists(userId)) { return null; } + mPermissionManager.enforceCrossUserPermission( + callingUid, userId, false, false, "getChangedPackages"); synchronized (mLock) { if (sequenceNumber >= mChangedPackagesSequenceNumber) { return null; @@ -8800,9 +8812,23 @@ public class PackageManagerService extends IPackageManager.Stub private ProviderInfo resolveContentProviderInternal(String name, int flags, int userId) { if (!mUserManager.exists(userId)) return null; - flags = updateFlagsForComponent(flags, userId); final int callingUid = Binder.getCallingUid(); + flags = updateFlagsForComponent(flags, userId); final ProviderInfo providerInfo = mComponentResolver.queryProvider(name, flags, userId); + boolean checkedGrants = false; + if (providerInfo != null) { + // Looking for cross-user grants before enforcing the typical cross-users permissions + if (userId != UserHandle.getUserId(callingUid)) { + final UriGrantsManagerInternal mUgmInternal = + LocalServices.getService(UriGrantsManagerInternal.class); + checkedGrants = + mUgmInternal.checkAuthorityGrants(callingUid, providerInfo, userId, true); + } + } + if (!checkedGrants) { + mPermissionManager.enforceCrossUserPermission( + callingUid, userId, false, false, "resolveContentProvider"); + } if (providerInfo == null) { return null; } @@ -16267,10 +16293,16 @@ public class PackageManagerService extends IPackageManager.Stub // signing certificate than the existing one, and if so, copy over the new // details if (signatureCheckPs.sharedUser != null) { - if (parsedPackage.getSigningDetails().hasAncestor( - signatureCheckPs.sharedUser.signatures.mSigningDetails)) { - signatureCheckPs.sharedUser.signatures.mSigningDetails = - parsedPackage.getSigningDetails(); + // Attempt to merge the existing lineage for the shared SigningDetails with + // the lineage of the new package; if the shared SigningDetails are not + // returned this indicates the new package added new signers to the lineage + // and/or changed the capabilities of existing signers in the lineage. + SigningDetails sharedSigningDetails = + signatureCheckPs.sharedUser.signatures.mSigningDetails; + SigningDetails mergedDetails = sharedSigningDetails.mergeLineageWith( + signingDetails); + if (mergedDetails != sharedSigningDetails) { + signatureCheckPs.sharedUser.signatures.mSigningDetails = mergedDetails; } if (signatureCheckPs.sharedUser.signaturesChanged == null) { signatureCheckPs.sharedUser.signaturesChanged = Boolean.FALSE; diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 5c175a6ef847..1fce07b0cbf1 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -611,7 +611,6 @@ public class PackageManagerServiceUtils { final String packageName = pkgSetting.name; boolean compatMatch = false; if (pkgSetting.signatures.mSigningDetails.signatures != null) { - // Already existing package. Make sure signatures match boolean match = parsedSignatures.checkCapability( pkgSetting.signatures.mSigningDetails, @@ -664,6 +663,13 @@ public class PackageManagerServiceUtils { || pkgSetting.getSharedUser().signatures.mSigningDetails.checkCapability( parsedSignatures, PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID); + // Special case: if the sharedUserId capability check failed it could be due to this + // being the only package in the sharedUserId so far and the lineage being updated to + // deny the sharedUserId capability of the previous key in the lineage. + if (!match && pkgSetting.getSharedUser().packages.size() == 1 + && pkgSetting.getSharedUser().packages.valueAt(0).name.equals(packageName)) { + match = true; + } if (!match && compareCompat) { match = matchSignaturesCompat( packageName, pkgSetting.getSharedUser().signatures, parsedSignatures); @@ -686,6 +692,42 @@ public class PackageManagerServiceUtils { + " has no signatures that match those in shared user " + pkgSetting.getSharedUser().name + "; ignoring!"); } + // It is possible that this package contains a lineage that blocks sharedUserId access + // to an already installed package in the sharedUserId signed with a previous key. + // Iterate over all of the packages in the sharedUserId and ensure any that are signed + // with a key in this package's lineage have the SHARED_USER_ID capability granted. + if (parsedSignatures.hasPastSigningCertificates()) { + for (PackageSetting shUidPkgSetting : pkgSetting.getSharedUser().packages) { + // if the current package in the sharedUserId is the package being updated then + // skip this check as the update may revoke the sharedUserId capability from + // the key with which this app was previously signed. + if (packageName.equals(shUidPkgSetting.name)) { + continue; + } + PackageParser.SigningDetails shUidSigningDetails = + shUidPkgSetting.getSigningDetails(); + // The capability check only needs to be performed against the package if it is + // signed with a key that is in the lineage of the package being installed. + if (parsedSignatures.hasAncestor(shUidSigningDetails)) { + if (!parsedSignatures.checkCapability(shUidSigningDetails, + PackageParser.SigningDetails.CertCapabilities.SHARED_USER_ID)) { + throw new PackageManagerException( + INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, + "Package " + packageName + + " revoked the sharedUserId capability from the " + + "signing key used to sign " + shUidPkgSetting.name); + } + } + } + } + // If the lineage of this package diverges from the lineage of the sharedUserId then + // do not allow the installation to proceed. + if (!parsedSignatures.hasCommonAncestor( + pkgSetting.getSharedUser().signatures.mSigningDetails)) { + throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE, + "Package " + packageName + " has a signing lineage " + + "that diverges from the lineage of the sharedUserId"); + } } return compatMatch; } 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/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java index 1fec8aa0a3ff..14d043c371e2 100644 --- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java +++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java @@ -206,21 +206,9 @@ public class UserRestrictionsUtils { */ private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS = Sets.newArraySet( - UserManager.DISALLOW_CONFIG_DATE_TIME, - UserManager.DISALLOW_CAMERA, - UserManager.DISALLOW_BLUETOOTH, - UserManager.DISALLOW_BLUETOOTH_SHARING, - UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, - UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, - UserManager.DISALLOW_CONFIG_PRIVATE_DNS, - UserManager.DISALLOW_CONFIG_TETHERING, - UserManager.DISALLOW_DATA_ROAMING, - UserManager.DISALLOW_SAFE_BOOT, - UserManager.DISALLOW_SMS, - UserManager.DISALLOW_USB_FILE_TRANSFER, UserManager.DISALLOW_AIRPLANE_MODE, - UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, - UserManager.DISALLOW_UNMUTE_MICROPHONE + UserManager.DISALLOW_CONFIG_DATE_TIME, + UserManager.DISALLOW_CONFIG_PRIVATE_DNS ); /** @@ -236,7 +224,19 @@ public class UserRestrictionsUtils { UserManager.DISALLOW_CONTENT_SUGGESTIONS, UserManager.DISALLOW_DEBUGGING_FEATURES, UserManager.DISALLOW_SHARE_LOCATION, - UserManager.DISALLOW_OUTGOING_CALLS + UserManager.DISALLOW_OUTGOING_CALLS, + UserManager.DISALLOW_CAMERA, + UserManager.DISALLOW_BLUETOOTH, + UserManager.DISALLOW_BLUETOOTH_SHARING, + UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, + UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, + UserManager.DISALLOW_CONFIG_TETHERING, + UserManager.DISALLOW_DATA_ROAMING, + UserManager.DISALLOW_SAFE_BOOT, + UserManager.DISALLOW_SMS, + UserManager.DISALLOW_USB_FILE_TRANSFER, + UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, + UserManager.DISALLOW_UNMUTE_MICROPHONE ); /** 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/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index e3b1152cd7b7..323ac7b8806e 100755 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -16,6 +16,7 @@ package com.android.server.tv; +import static android.media.AudioManager.DEVICE_NONE; import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED; import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY; @@ -2047,6 +2048,36 @@ public final class TvInputManagerService extends SystemService { return clientPid; } + /** + * Add a hardware device in the TvInputHardwareManager for CTS testing + * purpose. + * + * @param device id of the adding hardware device. + */ + @Override + public void addHardwareDevice(int deviceId) { + TvInputHardwareInfo info = new TvInputHardwareInfo.Builder() + .deviceId(deviceId) + .type(TvInputHardwareInfo.TV_INPUT_TYPE_HDMI) + .audioType(DEVICE_NONE) + .audioAddress("0") + .hdmiPortId(0) + .build(); + mTvInputHardwareManager.onDeviceAvailable(info, null); + return; + } + + /** + * Remove a hardware device in the TvInputHardwareManager for CTS testing + * purpose. + * + * @param device id of the removing hardware device. + */ + @Override + public void removeHardwareDevice(int deviceId) { + mTvInputHardwareManager.onDeviceUnavailable(deviceId); + } + private int getClientPidLocked(String sessionId) throws IllegalStateException { if (mSessionIdToSessionStateMap.get(sessionId) == null) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index b5b82d39b921..5a6e0a1ff9c3 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -6535,14 +6535,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Configuration resolvedConfig = getResolvedOverrideConfiguration(); final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); final int requestedOrientation = getRequestedConfigurationOrientation(); - final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED; + final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED + && !mDisplayContent.ignoreRotationForApps(); final int orientation = orientationRequested ? requestedOrientation : newParentConfiguration.orientation; int rotation = newParentConfiguration.windowConfiguration.getRotation(); final boolean canChangeOrientation = handlesOrientationChangeFromDescendant(); - if (canChangeOrientation && mCompatDisplayInsets.mIsRotatable - && !mCompatDisplayInsets.mIsFloating) { + if (canChangeOrientation && !mCompatDisplayInsets.mIsFloating) { // Use parent rotation because the original display can rotate by requested orientation. resolvedConfig.windowConfiguration.setRotation(rotation); } else { @@ -7628,7 +7628,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private final int mWidth; private final int mHeight; final boolean mIsFloating; - final boolean mIsRotatable; /** * The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It @@ -7645,7 +7644,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** Constructs the environment to simulate the bounds behavior of the given container. */ CompatDisplayInsets(DisplayContent display, WindowContainer container) { mIsFloating = container.getWindowConfiguration().tasksAreFloating(); - mIsRotatable = !mIsFloating && !display.ignoreRotationForApps(); if (mIsFloating) { final Rect containerBounds = container.getWindowConfiguration().getBounds(); mWidth = containerBounds.width(); @@ -7702,7 +7700,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } - if (mIsRotatable && canChangeOrientation) { + if (canChangeOrientation) { getBoundsByRotation(outBounds, rotation); if (orientationRequested) { getFrameByOrientation(outAppBounds, orientation); diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 9b9b61340332..fc69ef555986 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -2638,6 +2638,9 @@ class ActivityStack extends Task { mRootWindowContainer.ensureVisibilityAndConfig(null /* starting */, getDisplay().mDisplayId, false /* markFrozenIfConfigChanged */, false /* deferResume */); + // Usually resuming a top activity triggers the next app transition, but nothing's got + // resumed in this case, so we need to execute it explicitly. + getDisplay().mDisplayContent.executeAppTransition(); } else { mRootWindowContainer.resumeFocusedStacksTopActivities(); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index eb85db61754f..3272a5d90d4e 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -99,6 +99,7 @@ import static com.android.server.wm.DisplayContentProto.WINDOW_CONTAINER; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; +import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_SCREEN_ON; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS; @@ -498,6 +499,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ private ActivityRecord mFixedRotationLaunchingApp; + private FixedRotationAnimationController mFixedRotationAnimationController; + final FixedRotationTransitionListener mFixedRotationTransitionListener = new FixedRotationTransitionListener(); @@ -1106,12 +1109,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } void removeShellRoot(int windowType) { - ShellRoot root = mShellRoots.get(windowType); - if (root == null) { - return; + synchronized(mWmService.mGlobalLock) { + ShellRoot root = mShellRoots.get(windowType); + if (root == null) { + return; + } + root.clear(); + mShellRoots.remove(windowType); } - root.clear(); - mShellRoots.remove(windowType); } void setRemoteInsetsController(IDisplayWindowInsetsController controller) { @@ -1485,6 +1490,11 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mFixedRotationLaunchingApp; } + @VisibleForTesting + @Nullable FixedRotationAnimationController getFixedRotationAnimationController() { + return mFixedRotationAnimationController; + } + void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r) { setFixedRotationLaunchingAppUnchecked(r, ROTATION_UNDEFINED); } @@ -1492,8 +1502,13 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo void setFixedRotationLaunchingAppUnchecked(@Nullable ActivityRecord r, int rotation) { if (mFixedRotationLaunchingApp == null && r != null) { mWmService.mDisplayNotificationController.dispatchFixedRotationStarted(this, rotation); + if (mFixedRotationAnimationController == null) { + mFixedRotationAnimationController = new FixedRotationAnimationController(this); + mFixedRotationAnimationController.hide(); + } } else if (mFixedRotationLaunchingApp != null && r == null) { mWmService.mDisplayNotificationController.dispatchFixedRotationFinished(this); + finishFixedRotationAnimationIfPossible(); } mFixedRotationLaunchingApp = r; } @@ -1582,6 +1597,15 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo } } + /** Re-show the previously hidden windows if all seamless rotated windows are done. */ + void finishFixedRotationAnimationIfPossible() { + final FixedRotationAnimationController controller = mFixedRotationAnimationController; + if (controller != null && !mDisplayRotation.hasSeamlessRotatingWindow()) { + controller.show(); + mFixedRotationAnimationController = null; + } + } + /** * Update rotation of the display. * @@ -3494,7 +3518,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) { return; } - + ProtoLog.i(WM_DEBUG_IME, "setInputMethodTarget %s", target); mInputMethodTarget = target; mInputMethodTargetWaitingAnim = targetWaitingAnim; assignWindowLayers(true /* setLayoutNeeded */); @@ -3508,6 +3532,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo */ void setInputMethodInputTarget(WindowState target) { if (mInputMethodInputTarget != target) { + ProtoLog.i(WM_DEBUG_IME, "setInputMethodInputTarget %s", target); mInputMethodInputTarget = target; updateImeControlTarget(); } @@ -3515,6 +3540,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private void updateImeControlTarget() { mInputMethodControlTarget = computeImeControlTarget(); + ProtoLog.i(WM_DEBUG_IME, "updateImeControlTarget %s", + mInputMethodControlTarget.getWindow()); mInsetsStateController.onImeControlTargetChanged(mInputMethodControlTarget); } diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 702df2a0fc63..96f236310d83 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -560,6 +560,7 @@ public class DisplayRotation { }, true /* traverseTopToBottom */); mSeamlessRotationCount = 0; mRotatingSeamlessly = false; + mDisplayContent.finishFixedRotationAnimationIfPossible(); } private void prepareSeamlessRotation() { @@ -573,6 +574,10 @@ public class DisplayRotation { return mRotatingSeamlessly; } + boolean hasSeamlessRotatingWindow() { + return mSeamlessRotationCount > 0; + } + @VisibleForTesting boolean shouldRotateSeamlessly(int oldRotation, int newRotation, boolean forceUpdate) { // Display doesn't need to be frozen because application has been started in correct @@ -646,6 +651,7 @@ public class DisplayRotation { "Performing post-rotate rotation after seamless rotation"); // Finish seamless rotation. mRotatingSeamlessly = false; + mDisplayContent.finishFixedRotationAnimationIfPossible(); updateRotationAndSendNewConfigIfChanged(); } diff --git a/services/core/java/com/android/server/wm/FixedRotationAnimationController.java b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java new file mode 100644 index 000000000000..cc02e991c2ae --- /dev/null +++ b/services/core/java/com/android/server/wm/FixedRotationAnimationController.java @@ -0,0 +1,166 @@ +/* + * 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.server.wm; + +import static com.android.server.wm.AnimationSpecProto.WINDOW; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM; +import static com.android.server.wm.WindowAnimationSpecProto.ANIMATION; + +import android.content.Context; +import android.util.ArrayMap; +import android.util.proto.ProtoOutputStream; +import android.view.SurfaceControl; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.Transformation; + +import com.android.internal.R; + +import java.io.PrintWriter; +import java.util.ArrayList; + +/** + * Controller to fade out and in system ui when applying a fixed rotation transform to a window + * token. + * + * The system bars will be fade out when the fixed rotation transform starts and will be fade in + * once all surfaces have been rotated. + */ +public class FixedRotationAnimationController { + + private final Context mContext; + private final WindowState mStatusBar; + private final WindowState mNavigationBar; + private final ArrayList<WindowToken> mAnimatedWindowToken = new ArrayList<>(2); + private final ArrayMap<WindowToken, Runnable> mDeferredFinishCallbacks = new ArrayMap<>(); + + public FixedRotationAnimationController(DisplayContent displayContent) { + mContext = displayContent.mWmService.mContext; + final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy(); + mStatusBar = displayPolicy.getStatusBar(); + // Do not animate movable navigation bar (e.g. non-gesture mode). + mNavigationBar = !displayPolicy.navigationBarCanMove() + ? displayPolicy.getNavigationBar() + : null; + } + + /** Applies show animation on the previously hidden window tokens. */ + void show() { + for (int i = mAnimatedWindowToken.size() - 1; i >= 0; i--) { + final WindowToken windowToken = mAnimatedWindowToken.get(i); + fadeWindowToken(true /* show */, windowToken); + } + } + + /** Applies hide animation on the window tokens which may be seamlessly rotated later. */ + void hide() { + if (mNavigationBar != null) { + fadeWindowToken(false /* show */, mNavigationBar.mToken); + } + if (mStatusBar != null) { + fadeWindowToken(false /* show */, mStatusBar.mToken); + } + } + + private void fadeWindowToken(boolean show, WindowToken windowToken) { + if (windowToken == null || windowToken.getParent() == null) { + return; + } + + final Animation animation = AnimationUtils.loadAnimation(mContext, + show ? R.anim.fade_in : R.anim.fade_out); + final LocalAnimationAdapter.AnimationSpec windowAnimationSpec = + createAnimationSpec(animation); + + final FixedRotationAnimationAdapter animationAdapter = new FixedRotationAnimationAdapter( + windowAnimationSpec, windowToken.getSurfaceAnimationRunner(), show, windowToken); + + // We deferred the end of the animation when hiding the token, so we need to end it now that + // it's shown again. + final SurfaceAnimator.OnAnimationFinishedCallback finishedCallback = show ? (t, r) -> { + final Runnable runnable = mDeferredFinishCallbacks.remove(windowToken); + if (runnable != null) { + runnable.run(); + } + } : null; + windowToken.startAnimation(windowToken.getPendingTransaction(), animationAdapter, + show /* hidden */, ANIMATION_TYPE_FIXED_TRANSFORM, finishedCallback); + mAnimatedWindowToken.add(windowToken); + } + + private LocalAnimationAdapter.AnimationSpec createAnimationSpec(Animation animation) { + return new LocalAnimationAdapter.AnimationSpec() { + + final Transformation mTransformation = new Transformation(); + + @Override + public boolean getShowWallpaper() { + return true; + } + + @Override + public long getDuration() { + return animation.getDuration(); + } + + @Override + public void apply(SurfaceControl.Transaction t, SurfaceControl leash, + long currentPlayTime) { + mTransformation.clear(); + animation.getTransformation(currentPlayTime, mTransformation); + t.setAlpha(leash, mTransformation.getAlpha()); + } + + @Override + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); + pw.println(animation); + } + + @Override + public void dumpDebugInner(ProtoOutputStream proto) { + final long token = proto.start(WINDOW); + proto.write(ANIMATION, animation.toString()); + proto.end(token); + } + }; + } + + private class FixedRotationAnimationAdapter extends LocalAnimationAdapter { + private final boolean mShow; + private final WindowToken mToken; + + FixedRotationAnimationAdapter(AnimationSpec windowAnimationSpec, + SurfaceAnimationRunner surfaceAnimationRunner, boolean show, + WindowToken token) { + super(windowAnimationSpec, surfaceAnimationRunner); + mShow = show; + mToken = token; + } + + @Override + public boolean shouldDeferAnimationFinish(Runnable endDeferFinishCallback) { + // We defer the end of the hide animation to ensure the tokens stay hidden until + // we show them again. + if (!mShow) { + mDeferredFinishCallbacks.put(mToken, endDeferFinishCallback); + return true; + } + return false; + } + } +} diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 7491376cd152..a0985fc48422 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -65,9 +65,16 @@ class ImeInsetsSourceProvider extends InsetsSourceProvider { // Target should still be the same. if (isImeTargetFromDisplayContentAndImeSame()) { final InsetsControlTarget target = mDisplayContent.mInputMethodControlTarget; - ProtoLog.d(WM_DEBUG_IME, "call showInsets(ime) on %s", + + ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s", target.getWindow() != null ? target.getWindow().getName() : ""); target.showInsets(WindowInsets.Type.ime(), true /* fromIme */); + if (target != mImeTargetFromIme && mImeTargetFromIme != null) { + ProtoLog.w(WM_DEBUG_IME, + "showInsets(ime) was requested by different window: %s ", + (mImeTargetFromIme.getWindow() != null + ? mImeTargetFromIme.getWindow().getName() : "")); + } } abortShowImePostLayout(); }; diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index a6a21fc55770..6a4975950f1a 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -24,6 +24,7 @@ import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME; import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE; import static android.view.ViewRootImpl.sNewInsetsMode; +import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_INSETS_CONTROL; import static com.android.server.wm.WindowManagerService.H.LAYOUT_AND_ASSIGN_WINDOW_LAYERS_IF_NEEDED; @@ -40,6 +41,7 @@ import android.view.SurfaceControl.Transaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.function.TriConsumer; +import com.android.server.protolog.common.ProtoLog; import com.android.server.wm.SurfaceAnimator.AnimationType; import com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; @@ -134,6 +136,7 @@ class InsetsSourceProvider { // animate-out as new one animates-in. mWin.cancelAnimation(); } + ProtoLog.d(WM_DEBUG_IME, "InsetsSource setWin %s", win); mWin = win; mFrameProvider = frameProvider; mImeFrameProvider = imeFrameProvider; @@ -299,6 +302,8 @@ class InsetsSourceProvider { updateVisibility(); mControl = new InsetsSourceControl(mSource.getType(), leash, new Point(mWin.getWindowFrames().mFrame.left, mWin.getWindowFrames().mFrame.top)); + ProtoLog.d(WM_DEBUG_IME, + "InsetsSource Control %s for target %s", mControl, mControlTarget); } void startSeamlessRotation() { @@ -349,6 +354,9 @@ class InsetsSourceProvider { final boolean isClientControlled = mControlTarget != null && mControlTarget.isClientControlled(); mSource.setVisible(mServerVisible && (!isClientControlled || mClientVisible)); + ProtoLog.d(WM_DEBUG_IME, + "InsetsSource updateVisibility serverVisible: %s clientVisible: %s", + mServerVisible, mClientVisible); } InsetsSourceControl getControl(InsetsControlTarget target) { @@ -391,6 +399,44 @@ class InsetsSourceProvider { return mImeOverrideFrame; } + public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "InsetsSourceProvider"); + pw.print(prefix + " mSource="); mSource.dump(prefix + " ", pw); + if (mControl != null) { + pw.print(prefix + " mControl="); + mControl.dump(prefix + " ", pw); + } + pw.print(prefix + " mFakeControl="); mFakeControl.dump(prefix + " ", pw); + pw.print(" mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching); + pw.print(" mImeOverrideFrame="); pw.print(mImeOverrideFrame.toString()); + if (mWin != null) { + pw.print(prefix + " mWin="); + mWin.dump(pw, prefix + " ", false /* dumpAll */); + } + if (mAdapter != null) { + pw.print(prefix + " mAdapter="); + mAdapter.dump(pw, prefix + " "); + } + if (mControlTarget != null) { + pw.print(prefix + " mControlTarget="); + if (mControlTarget.getWindow() != null) { + mControlTarget.getWindow().dump(pw, prefix + " ", false /* dumpAll */); + } + } + if (mPendingControlTarget != null) { + pw.print(prefix + " mPendingControlTarget="); + if (mPendingControlTarget.getWindow() != null) { + mPendingControlTarget.getWindow().dump(pw, prefix + " ", false /* dumpAll */); + } + } + if (mFakeControlTarget != null) { + pw.print(prefix + " mFakeControlTarget="); + if (mFakeControlTarget.getWindow() != null) { + mFakeControlTarget.getWindow().dump(pw, prefix + " ", false /* dumpAll */); + } + } + } + private class ControlAdapter implements AnimationAdapter { private SurfaceControl mCapturedLeash; @@ -410,6 +456,9 @@ class InsetsSourceProvider { t.setAlpha(animationLeash, 1 /* alpha */); t.hide(animationLeash); } + ProtoLog.i(WM_DEBUG_IME, + "ControlAdapter startAnimation mSource: %s controlTarget: %s", mSource, + mControlTarget); mCapturedLeash = animationLeash; final Rect frame = mWin.getWindowFrames().mFrame; @@ -424,6 +473,9 @@ class InsetsSourceProvider { mControlTarget = null; mAdapter = null; setClientVisible(InsetsState.getDefaultVisibility(mSource.getType())); + ProtoLog.i(WM_DEBUG_IME, + "ControlAdapter onAnimationCancelled mSource: %s mControlTarget: %s", + mSource, mControlTarget); } } @@ -439,6 +491,8 @@ class InsetsSourceProvider { @Override public void dump(PrintWriter pw, String prefix) { + pw.println(prefix + "ControlAdapter"); + pw.print(prefix + " mCapturedLeash="); pw.print(mCapturedLeash); } @Override diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 9798d77c5975..77bc37f0c2d7 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -29,6 +29,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR; +import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.WindowConfiguration; @@ -42,6 +44,8 @@ import android.view.InsetsState; import android.view.InsetsState.InternalInsetsType; import android.view.WindowManager; +import com.android.server.protolog.common.ProtoLog; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.function.Consumer; @@ -289,7 +293,10 @@ class InsetsStateController { // Make sure that we always have a control target for the IME, even if the IME target is // null. Otherwise there is no leash that will hide it and IME becomes "randomly" visible. - onControlChanged(ITYPE_IME, imeTarget != null ? imeTarget : mEmptyImeControlTarget); + InsetsControlTarget target = imeTarget != null ? imeTarget : mEmptyImeControlTarget; + onControlChanged(ITYPE_IME, target); + ProtoLog.d(WM_DEBUG_IME, "onImeControlTargetChanged %s", + target != null ? target.getWindow() : "null"); notifyPendingInsetsControlChanged(); } @@ -440,5 +447,11 @@ class InsetsStateController { pw.println(InsetsState.typeToString(mTypeControlTargetMap.keyAt(i)) + " -> " + mTypeControlTargetMap.valueAt(i)); } + pw.println(prefix + " " + "InsetsSourceProviders map:"); + for (int i = mProviders.size() - 1; i >= 0; i--) { + pw.print(prefix + " "); + pw.println(InsetsState.typeToString(mProviders.keyAt(i)) + " -> "); + mProviders.valueAt(i).dump(pw, prefix); + } } } diff --git a/services/core/java/com/android/server/wm/ShellRoot.java b/services/core/java/com/android/server/wm/ShellRoot.java index 99f710bd8bd4..7a38bb65f73b 100644 --- a/services/core/java/com/android/server/wm/ShellRoot.java +++ b/services/core/java/com/android/server/wm/ShellRoot.java @@ -156,4 +156,3 @@ public class ShellRoot { } } } - diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java index 42342a60ba16..0143eb1abe03 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimator.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java @@ -489,6 +489,12 @@ class SurfaceAnimator { static final int ANIMATION_TYPE_INSETS_CONTROL = 1 << 5; /** + * Animation when a fixed rotation transform is applied to a window token. + * @hide + */ + static final int ANIMATION_TYPE_FIXED_TRANSFORM = 1 << 6; + + /** * Bitmask to include all animation types. This is NOT an {@link AnimationType} * @hide */ @@ -505,7 +511,8 @@ class SurfaceAnimator { ANIMATION_TYPE_DIMMER, ANIMATION_TYPE_RECENTS, ANIMATION_TYPE_WINDOW_ANIMATION, - ANIMATION_TYPE_INSETS_CONTROL + ANIMATION_TYPE_INSETS_CONTROL, + ANIMATION_TYPE_FIXED_TRANSFORM }) @Retention(RetentionPolicy.SOURCE) @interface AnimationType {} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 159c59b86b43..eee4e7795252 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -6191,6 +6191,7 @@ public class WindowManagerService extends IWindowManager.Stub final int displayId = dc.getDisplayId(); final WindowState inputMethodTarget = dc.mInputMethodTarget; final WindowState inputMethodInputTarget = dc.mInputMethodInputTarget; + final InsetsControlTarget inputMethodControlTarget = dc.mInputMethodControlTarget; if (inputMethodTarget != null) { pw.print(" mInputMethodTarget in display# "); pw.print(displayId); pw.print(' '); pw.println(inputMethodTarget); @@ -6199,6 +6200,10 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mInputMethodInputTarget in display# "); pw.print(displayId); pw.print(' '); pw.println(inputMethodInputTarget); } + if (inputMethodControlTarget != null) { + pw.print(" inputMethodControlTarget in display# "); pw.print(displayId); + pw.print(' '); pw.println(inputMethodControlTarget.getWindow()); + } }); pw.print(" mInTouchMode="); pw.println(mInTouchMode); pw.print(" mLastDisplayFreezeDuration="); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index e9aff88d0f80..4f1893eb6c13 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -113,6 +113,7 @@ import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT; +import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_IME; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RESIZE; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW; @@ -334,6 +335,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP private boolean mDragResizing; private boolean mDragResizingChangeReported = true; private int mResizeMode; + private boolean mResizeForBlastSyncReported; + /** * Special mode that is intended only for the rounded corner overlay: during rotation * transition, we un-rotate the window token such that the window appears as it did before the @@ -1370,11 +1373,14 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP // variables, because mFrameSizeChanged only tracks the width and height changing. updateLastFrames(); + // Add a window that is using blastSync to the resizing list if it hasn't been reported + // already. This because the window is waiting on a finishDrawing from the client. if (didFrameInsetsChange || winAnimator.mSurfaceResized || configChanged || dragResizingChanged - || mReportOrientationChanged) { + || mReportOrientationChanged + || requestResizeForBlastSync()) { ProtoLog.v(WM_DEBUG_RESIZE, "Resize reasons for w=%s: %s surfaceResized=%b configChanged=%b " + "dragResizingChanged=%b reportOrientationChanged=%b", @@ -3483,6 +3489,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mReportOrientationChanged = false; mDragResizingChangeReported = true; mWinAnimator.mSurfaceResized = false; + mResizeForBlastSyncReported = true; mWindowFrames.resetInsetsChanged(); final Rect frame = mWindowFrames.mCompatFrame; @@ -3553,6 +3560,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP * Called when the insets state changed. */ void notifyInsetsChanged() { + ProtoLog.d(WM_DEBUG_IME, "notifyInsetsChanged for %s ", this); try { mClient.insetsChanged(getInsetsState()); } catch (RemoteException e) { @@ -3562,6 +3570,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP @Override public void notifyInsetsControlChanged() { + ProtoLog.d(WM_DEBUG_IME, "notifyInsetsControlChanged for %s ", this); final InsetsStateController stateController = getDisplayContent().getInsetsStateController(); try { @@ -5500,7 +5509,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } long getFrameNumber() { - return mFrameNumber; + // Return the frame number in which changes requested in this layout will be rendered or + // -1 if we do not expect the frame to be rendered. + return getFrameLw().isEmpty() ? -1 : mFrameNumber; } void setFrameNumber(long frameNumber) { @@ -5733,6 +5744,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (!willSync) { return false; } + mResizeForBlastSyncReported = false; mLocalSyncId = mBLASTSyncEngine.startSyncSet(this); addChildrenToSyncSet(mLocalSyncId); @@ -5777,4 +5789,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mWaitingListener = null; return mWinAnimator.finishDrawingLocked(null); } + + private boolean requestResizeForBlastSync() { + return useBLASTSync() && !mResizeForBlastSyncReported; + } } diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp index d99299b5f07c..e790a196ad64 100644 --- a/services/incremental/BinderIncrementalService.cpp +++ b/services/incremental/BinderIncrementalService.cpp @@ -118,14 +118,18 @@ binder::Status BinderIncrementalService::openStorage(const std::string& path, } binder::Status BinderIncrementalService::createStorage( - const std::string& path, const content::pm::DataLoaderParamsParcel& params, - const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& listener, - int32_t createMode, int32_t* _aidl_return) { + const ::std::string& path, const ::android::content::pm::DataLoaderParamsParcel& params, + int32_t createMode, + const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& statusListener, + const ::android::os::incremental::StorageHealthCheckParams& healthCheckParams, + const ::android::sp<::android::os::incremental::IStorageHealthListener>& healthListener, + int32_t* _aidl_return) { *_aidl_return = mImpl.createStorage(path, const_cast<content::pm::DataLoaderParamsParcel&&>(params), - listener, - android::incremental::IncrementalService::CreateOptions( - createMode)); + android::incremental::IncrementalService::CreateOptions(createMode), + statusListener, + const_cast<StorageHealthCheckParams&&>(healthCheckParams), + healthListener); return ok(); } diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h index 659f6afcc03f..68549f5a8ff8 100644 --- a/services/incremental/BinderIncrementalService.h +++ b/services/incremental/BinderIncrementalService.h @@ -41,8 +41,11 @@ public: binder::Status openStorage(const std::string& path, int32_t* _aidl_return) final; binder::Status createStorage( const ::std::string& path, const ::android::content::pm::DataLoaderParamsParcel& params, - const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& listener, - int32_t createMode, int32_t* _aidl_return) final; + int32_t createMode, + const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& statusListener, + const ::android::os::incremental::StorageHealthCheckParams& healthCheckParams, + const ::android::sp<IStorageHealthListener>& healthListener, + int32_t* _aidl_return) final; binder::Status createLinkedStorage(const std::string& path, int32_t otherStorageId, int32_t createMode, int32_t* _aidl_return) final; binder::Status makeBindMount(int32_t storageId, const std::string& sourcePath, @@ -55,8 +58,7 @@ public: binder::Status makeDirectories(int32_t storageId, const std::string& path, int32_t* _aidl_return) final; binder::Status makeFile(int32_t storageId, const std::string& path, - const ::android::os::incremental::IncrementalNewFileParams& params, - int32_t* _aidl_return) final; + const IncrementalNewFileParams& params, int32_t* _aidl_return) final; binder::Status makeFileFromRange(int32_t storageId, const std::string& targetPath, const std::string& sourcePath, int64_t start, int64_t end, int32_t* _aidl_return) final; diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp index bf859b9b914d..66c7717d7987 100644 --- a/services/incremental/IncrementalService.cpp +++ b/services/incremental/IncrementalService.cpp @@ -410,9 +410,12 @@ auto IncrementalService::getStorageSlotLocked() -> MountMap::iterator { } } -StorageId IncrementalService::createStorage( - std::string_view mountPoint, DataLoaderParamsParcel&& dataLoaderParams, - const DataLoaderStatusListener& dataLoaderStatusListener, CreateOptions options) { +StorageId IncrementalService::createStorage(std::string_view mountPoint, + content::pm::DataLoaderParamsParcel&& dataLoaderParams, + CreateOptions options, + const DataLoaderStatusListener& statusListener, + StorageHealthCheckParams&& healthCheckParams, + const StorageHealthListener& healthListener) { LOG(INFO) << "createStorage: " << mountPoint << " | " << int(options); if (!path::isAbsolute(mountPoint)) { LOG(ERROR) << "path is not absolute: " << mountPoint; @@ -545,8 +548,8 @@ StorageId IncrementalService::createStorage( // Done here as well, all data structures are in good state. secondCleanupOnFailure.release(); - auto dataLoaderStub = - prepareDataLoader(*ifs, std::move(dataLoaderParams), &dataLoaderStatusListener); + auto dataLoaderStub = prepareDataLoader(*ifs, std::move(dataLoaderParams), &statusListener, + std::move(healthCheckParams), &healthListener); CHECK(dataLoaderStub); mountIt->second = std::move(ifs); @@ -1254,7 +1257,7 @@ bool IncrementalService::mountExistingImage(std::string_view root) { dataLoaderParams.arguments = loader.arguments(); } - prepareDataLoader(*ifs, std::move(dataLoaderParams), nullptr); + prepareDataLoader(*ifs, std::move(dataLoaderParams)); CHECK(ifs->dataLoaderStub); std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints; @@ -1338,14 +1341,18 @@ void IncrementalService::runCmdLooper() { IncrementalService::DataLoaderStubPtr IncrementalService::prepareDataLoader( IncFsMount& ifs, DataLoaderParamsParcel&& params, - const DataLoaderStatusListener* externalListener) { + const DataLoaderStatusListener* statusListener, + StorageHealthCheckParams&& healthCheckParams, const StorageHealthListener* healthListener) { std::unique_lock l(ifs.lock); - prepareDataLoaderLocked(ifs, std::move(params), externalListener); + prepareDataLoaderLocked(ifs, std::move(params), statusListener, std::move(healthCheckParams), + healthListener); return ifs.dataLoaderStub; } void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderParamsParcel&& params, - const DataLoaderStatusListener* externalListener) { + const DataLoaderStatusListener* statusListener, + StorageHealthCheckParams&& healthCheckParams, + const StorageHealthListener* healthListener) { if (ifs.dataLoaderStub) { LOG(INFO) << "Skipped data loader preparation because it already exists"; return; @@ -1360,7 +1367,8 @@ void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderPara ifs.dataLoaderStub = new DataLoaderStub(*this, ifs.mountId, std::move(params), std::move(fsControlParcel), - externalListener, path::join(ifs.root, constants().mount)); + statusListener, std::move(healthCheckParams), healthListener, + path::join(ifs.root, constants().mount)); } template <class Duration> @@ -1695,19 +1703,24 @@ void IncrementalService::onAppOpChanged(const std::string& packageName) { IncrementalService::DataLoaderStub::DataLoaderStub(IncrementalService& service, MountId id, DataLoaderParamsParcel&& params, FileSystemControlParcel&& control, - const DataLoaderStatusListener* externalListener, + const DataLoaderStatusListener* statusListener, + StorageHealthCheckParams&& healthCheckParams, + const StorageHealthListener* healthListener, std::string&& healthPath) : mService(service), mId(id), mParams(std::move(params)), mControl(std::move(control)), - mListener(externalListener ? *externalListener : DataLoaderStatusListener()), + mStatusListener(statusListener ? *statusListener : DataLoaderStatusListener()), + mHealthListener(healthListener ? *healthListener : StorageHealthListener()), mHealthPath(std::move(healthPath)) { + // TODO(b/153874006): enable external health listener. + mHealthListener = {}; healthStatusOk(); } IncrementalService::DataLoaderStub::~DataLoaderStub() { - if (mId != kInvalidStorageId) { + if (isValid()) { cleanupResources(); } } @@ -1725,13 +1738,14 @@ void IncrementalService::DataLoaderStub::cleanupResources() { mStatusCondition.wait_until(lock, now + 60s, [this] { return mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_DESTROYED; }); - mListener = {}; + mStatusListener = {}; + mHealthListener = {}; mId = kInvalidStorageId; } sp<content::pm::IDataLoader> IncrementalService::DataLoaderStub::getDataLoader() { sp<IDataLoader> dataloader; - auto status = mService.mDataLoaderManager->getDataLoader(mId, &dataloader); + auto status = mService.mDataLoaderManager->getDataLoader(id(), &dataloader); if (!status.isOk()) { LOG(ERROR) << "Failed to get dataloader: " << status.toString8(); return {}; @@ -1767,15 +1781,15 @@ void IncrementalService::DataLoaderStub::setTargetStatusLocked(int status) { auto oldStatus = mTargetStatus; mTargetStatus = status; mTargetStatusTs = Clock::now(); - LOG(DEBUG) << "Target status update for DataLoader " << mId << ": " << oldStatus << " -> " + LOG(DEBUG) << "Target status update for DataLoader " << id() << ": " << oldStatus << " -> " << status << " (current " << mCurrentStatus << ")"; } bool IncrementalService::DataLoaderStub::bind() { bool result = false; - auto status = mService.mDataLoaderManager->bindToDataLoader(mId, mParams, this, &result); + auto status = mService.mDataLoaderManager->bindToDataLoader(id(), mParams, this, &result); if (!status.isOk() || !result) { - LOG(ERROR) << "Failed to bind a data loader for mount " << mId; + LOG(ERROR) << "Failed to bind a data loader for mount " << id(); return false; } return true; @@ -1786,9 +1800,9 @@ bool IncrementalService::DataLoaderStub::create() { if (!dataloader) { return false; } - auto status = dataloader->create(mId, mParams, mControl, this); + auto status = dataloader->create(id(), mParams, mControl, this); if (!status.isOk()) { - LOG(ERROR) << "Failed to start DataLoader: " << status.toString8(); + LOG(ERROR) << "Failed to create DataLoader: " << status.toString8(); return false; } return true; @@ -1799,7 +1813,7 @@ bool IncrementalService::DataLoaderStub::start() { if (!dataloader) { return false; } - auto status = dataloader->start(mId); + auto status = dataloader->start(id()); if (!status.isOk()) { LOG(ERROR) << "Failed to start DataLoader: " << status.toString8(); return false; @@ -1808,7 +1822,7 @@ bool IncrementalService::DataLoaderStub::start() { } bool IncrementalService::DataLoaderStub::destroy() { - return mService.mDataLoaderManager->unbindFromDataLoader(mId).isOk(); + return mService.mDataLoaderManager->unbindFromDataLoader(id()).isOk(); } bool IncrementalService::DataLoaderStub::fsmStep() { @@ -1867,8 +1881,8 @@ binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mount return binder::Status:: fromServiceSpecificError(-EINVAL, "onStatusChange came to invalid DataLoaderStub"); } - if (mId != mountId) { - LOG(ERROR) << "Mount ID mismatch: expected " << mId << ", but got: " << mountId; + if (id() != mountId) { + LOG(ERROR) << "Mount ID mismatch: expected " << id() << ", but got: " << mountId; return binder::Status::fromServiceSpecificError(-EPERM, "Mount ID mismatch."); } @@ -1884,7 +1898,7 @@ binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mount mCurrentStatus = newStatus; targetStatus = mTargetStatus; - listener = mListener; + listener = mStatusListener; if (mCurrentStatus == IDataLoaderStatusListener::DATA_LOADER_UNAVAILABLE) { // For unavailable, unbind from DataLoader to ensure proper re-commit. @@ -1892,7 +1906,7 @@ binder::Status IncrementalService::DataLoaderStub::onStatusChanged(MountId mount } } - LOG(DEBUG) << "Current status update for DataLoader " << mId << ": " << oldStatus << " -> " + LOG(DEBUG) << "Current status update for DataLoader " << id() << ": " << oldStatus << " -> " << newStatus << " (target " << targetStatus << ")"; if (listener) { diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h index c2a06ae83465..05f62b977a85 100644 --- a/services/incremental/IncrementalService.h +++ b/services/incremental/IncrementalService.h @@ -21,6 +21,8 @@ #include <android/content/pm/FileSystemControlParcel.h> #include <android/content/pm/IDataLoaderStatusListener.h> #include <android/os/incremental/BnIncrementalServiceConnector.h> +#include <android/os/incremental/BnStorageHealthListener.h> +#include <android/os/incremental/StorageHealthCheckParams.h> #include <binder/IAppOpsCallback.h> #include <utils/String16.h> #include <utils/StrongPointer.h> @@ -56,10 +58,15 @@ using RawMetadata = incfs::RawMetadata; using Clock = std::chrono::steady_clock; using TimePoint = std::chrono::time_point<Clock>; using Seconds = std::chrono::seconds; +using BootClockTsUs = uint64_t; using IDataLoaderStatusListener = ::android::content::pm::IDataLoaderStatusListener; using DataLoaderStatusListener = ::android::sp<IDataLoaderStatusListener>; +using StorageHealthCheckParams = ::android::os::incremental::StorageHealthCheckParams; +using IStorageHealthListener = ::android::os::incremental::IStorageHealthListener; +using StorageHealthListener = ::android::sp<IStorageHealthListener>; + class IncrementalService final { public: explicit IncrementalService(ServiceManagerWrapper&& sm, std::string_view rootDir); @@ -72,6 +79,8 @@ public: static constexpr StorageId kInvalidStorageId = -1; static constexpr StorageId kMaxStorageId = std::numeric_limits<int>::max(); + static constexpr BootClockTsUs kMaxBootClockTsUs = std::numeric_limits<BootClockTsUs>::max(); + enum CreateOptions { TemporaryBind = 1, PermanentBind = 2, @@ -97,8 +106,9 @@ public: StorageId createStorage(std::string_view mountPoint, content::pm::DataLoaderParamsParcel&& dataLoaderParams, - const DataLoaderStatusListener& dataLoaderStatusListener, - CreateOptions options = CreateOptions::Default); + CreateOptions options, const DataLoaderStatusListener& statusListener, + StorageHealthCheckParams&& healthCheckParams, + const StorageHealthListener& healthListener); StorageId createLinkedStorage(std::string_view mountPoint, StorageId linkedStorage, CreateOptions options = CreateOptions::Default); StorageId openStorage(std::string_view path); @@ -162,7 +172,9 @@ private: DataLoaderStub(IncrementalService& service, MountId id, content::pm::DataLoaderParamsParcel&& params, content::pm::FileSystemControlParcel&& control, - const DataLoaderStatusListener* externalListener, std::string&& healthPath); + const DataLoaderStatusListener* statusListener, + StorageHealthCheckParams&& healthCheckParams, + const StorageHealthListener* healthListener, std::string&& healthPath); ~DataLoaderStub(); // Cleans up the internal state and invalidates DataLoaderStub. Any subsequent calls will // result in an error. @@ -213,7 +225,8 @@ private: MountId mId = kInvalidStorageId; content::pm::DataLoaderParamsParcel mParams; content::pm::FileSystemControlParcel mControl; - DataLoaderStatusListener mListener; + DataLoaderStatusListener mStatusListener; + StorageHealthListener mHealthListener; std::condition_variable mStatusCondition; int mCurrentStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED; @@ -292,9 +305,13 @@ private: DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs, content::pm::DataLoaderParamsParcel&& params, - const DataLoaderStatusListener* externalListener = nullptr); + const DataLoaderStatusListener* statusListener = nullptr, + StorageHealthCheckParams&& healthCheckParams = {}, + const StorageHealthListener* healthListener = nullptr); void prepareDataLoaderLocked(IncFsMount& ifs, content::pm::DataLoaderParamsParcel&& params, - const DataLoaderStatusListener* externalListener = nullptr); + const DataLoaderStatusListener* statusListener = nullptr, + StorageHealthCheckParams&& healthCheckParams = {}, + const StorageHealthListener* healthListener = nullptr); BindPathMap::const_iterator findStorageLocked(std::string_view path) const; StorageId findStorageId(std::string_view path) const; diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp index 08fb486c8058..a76aa625ebc6 100644 --- a/services/incremental/ServiceWrappers.cpp +++ b/services/incremental/ServiceWrappers.cpp @@ -175,6 +175,10 @@ public: ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final { return incfs::writeBlocks({blocks.data(), size_t(blocks.size())}); } + WaitResult waitForPendingReads(const Control& control, std::chrono::milliseconds timeout, + std::vector<incfs::ReadInfo>* pendingReadsBuffer) const final { + return incfs::waitForPendingReads(control, timeout, pendingReadsBuffer); + } }; RealServiceManager::RealServiceManager(sp<IServiceManager> serviceManager, JNIEnv* env) diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h index abbf2f4c4424..a935ab99267e 100644 --- a/services/incremental/ServiceWrappers.h +++ b/services/incremental/ServiceWrappers.h @@ -69,6 +69,7 @@ public: using Control = incfs::Control; using FileId = incfs::FileId; using ErrorCode = incfs::ErrorCode; + using WaitResult = incfs::WaitResult; using ExistingMountCallback = std::function<void(std::string_view root, std::string_view backingDir, @@ -90,6 +91,9 @@ public: virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0; virtual base::unique_fd openForSpecialOps(const Control& control, FileId id) const = 0; virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0; + virtual WaitResult waitForPendingReads( + const Control& control, std::chrono::milliseconds timeout, + std::vector<incfs::ReadInfo>* pendingReadsBuffer) const = 0; }; class AppOpsManagerWrapper { diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp index 2e4625cf85a1..2948b6a0f293 100644 --- a/services/incremental/test/IncrementalServiceTest.cpp +++ b/services/incremental/test/IncrementalServiceTest.cpp @@ -284,6 +284,9 @@ public: MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path)); MOCK_CONST_METHOD2(openForSpecialOps, base::unique_fd(const Control& control, FileId id)); MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks)); + MOCK_CONST_METHOD3(waitForPendingReads, + WaitResult(const Control& control, std::chrono::milliseconds timeout, + std::vector<incfs::ReadInfo>* pendingReadsBuffer)); MockIncFs() { ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return()); } @@ -292,12 +295,23 @@ public: void openMountSuccess() { ON_CALL(*this, openMount(_)).WillByDefault(Invoke(this, &MockIncFs::openMountForHealth)); } + void waitForPendingReadsSuccess() { + ON_CALL(*this, waitForPendingReads(_, _, _)) + .WillByDefault(Invoke(this, &MockIncFs::waitForPendingReadsForHealth)); + } static constexpr auto kPendingReadsFd = 42; Control openMountForHealth(std::string_view) { return UniqueControl(IncFs_CreateControl(-1, kPendingReadsFd, -1)); } + WaitResult waitForPendingReadsForHealth( + const Control& control, std::chrono::milliseconds timeout, + std::vector<incfs::ReadInfo>* pendingReadsBuffer) const { + pendingReadsBuffer->push_back({.bootClockTsUs = 0}); + return android::incfs::WaitResult::HaveData; + } + RawMetadata getMountInfoMetadata(const Control& control, std::string_view path) { metadata::Mount m; m.mutable_storage()->set_id(100); @@ -499,9 +513,9 @@ TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsFails) { mVold->mountIncFsFails(); EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -510,9 +524,9 @@ TEST_F(IncrementalServiceTest, testCreateStorageMountIncFsInvalidControlParcel) EXPECT_CALL(*mDataLoaderManager, bindToDataLoader(_, _, _, _)).Times(0); EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -523,9 +537,9 @@ TEST_F(IncrementalServiceTest, testCreateStorageMakeFileFails) { EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0); EXPECT_CALL(*mVold, unmountIncFs(_)); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -537,9 +551,9 @@ TEST_F(IncrementalServiceTest, testCreateStorageBindMountFails) { EXPECT_CALL(*mDataLoaderManager, unbindFromDataLoader(_)).Times(0); EXPECT_CALL(*mVold, unmountIncFs(_)); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -555,9 +569,9 @@ TEST_F(IncrementalServiceTest, testCreateStoragePrepareDataLoaderFails) { EXPECT_CALL(*mDataLoader, destroy(_)).Times(0); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_LT(storageId, 0); } @@ -574,9 +588,9 @@ TEST_F(IncrementalServiceTest, testDeleteStorageSuccess) { EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_GE(storageId, 0); mIncrementalService->deleteStorage(storageId); } @@ -594,9 +608,9 @@ TEST_F(IncrementalServiceTest, testDataLoaderDestroyed) { EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_GE(storageId, 0); // Simulated crash/other connection breakage. mDataLoaderManager->setDataLoaderStatusDestroyed(); @@ -616,9 +630,9 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderCreate) { EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_GE(storageId, 0); mDataLoaderManager->setDataLoaderStatusCreated(); ASSERT_TRUE(mIncrementalService->startLoading(storageId)); @@ -639,9 +653,9 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderPendingStart) { EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_GE(storageId, 0); ASSERT_TRUE(mIncrementalService->startLoading(storageId)); mDataLoaderManager->setDataLoaderStatusCreated(); @@ -661,9 +675,9 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderCreateUnavailable) { EXPECT_CALL(*mDataLoader, destroy(_)).Times(1); EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_GE(storageId, 0); mDataLoaderManager->setDataLoaderStatusUnavailable(); } @@ -672,6 +686,7 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) { mVold->mountIncFsSuccess(); mIncFs->makeFileSuccess(); mIncFs->openMountSuccess(); + mIncFs->waitForPendingReadsSuccess(); mVold->bindMountSuccess(); mDataLoader->initializeCreateOkNoStatus(); mDataLoaderManager->bindToDataLoaderSuccess(); @@ -685,9 +700,9 @@ TEST_F(IncrementalServiceTest, testStartDataLoaderRecreateOnPendingReads) { EXPECT_CALL(*mLooper, addFd(MockIncFs::kPendingReadsFd, _, _, _, _)).Times(1); EXPECT_CALL(*mLooper, removeFd(MockIncFs::kPendingReadsFd)).Times(1); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_GE(storageId, 0); mDataLoaderManager->setDataLoaderStatusUnavailable(); ASSERT_NE(nullptr, mLooper->mCallback); @@ -712,9 +727,9 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) { // Not expecting callback removal. EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_GE(storageId, 0); ASSERT_GE(mDataLoader->setStorageParams(true), 0); } @@ -739,9 +754,9 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccessAndPermissionChang // After callback is called, disable read logs and remove callback. EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(1); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_GE(storageId, 0); ASSERT_GE(mDataLoader->setStorageParams(true), 0); ASSERT_NE(nullptr, mAppOpsManager->mStoredCallback.get()); @@ -762,9 +777,9 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsCheckPermissionFails) { EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(0); EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_GE(storageId, 0); ASSERT_LT(mDataLoader->setStorageParams(true), 0); } @@ -785,9 +800,9 @@ TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) { EXPECT_CALL(*mAppOpsManager, startWatchingMode(_, _, _)).Times(0); EXPECT_CALL(*mAppOpsManager, stopWatchingMode(_)).Times(0); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); ASSERT_GE(storageId, 0); ASSERT_LT(mDataLoader->setStorageParams(true), 0); } @@ -799,9 +814,9 @@ TEST_F(IncrementalServiceTest, testMakeDirectory) { mDataLoaderManager->bindToDataLoaderSuccess(); mDataLoaderManager->getDataLoaderSuccess(); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); std::string dir_path("test"); // Expecting incfs to call makeDir on a path like: @@ -823,9 +838,9 @@ TEST_F(IncrementalServiceTest, testMakeDirectories) { mDataLoaderManager->bindToDataLoaderSuccess(); mDataLoaderManager->getDataLoaderSuccess(); TemporaryDir tempDir; - int storageId = - mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {}, - IncrementalService::CreateOptions::CreateNew); + int storageId = mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), + IncrementalService::CreateOptions::CreateNew, + {}, {}, {}); auto first = "first"sv; auto second = "second"sv; auto third = "third"sv; 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/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java index d338b587e059..ade01dc6afa6 100644 --- a/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/blob/BlobStoreManagerServiceTest.java @@ -19,7 +19,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealM import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS; +import static com.android.server.blob.BlobStoreConfig.DeviceConfigProperties.SESSION_EXPIRY_TIMEOUT_MS; import static com.google.common.truth.Truth.assertThat; @@ -93,6 +93,7 @@ public class BlobStoreManagerServiceTest { doReturn(true).when(mBlobsDir).exists(); doReturn(new File[0]).when(mBlobsDir).listFiles(); doReturn(true).when(() -> BlobStoreConfig.hasLeaseWaitTimeElapsed(anyLong())); + doCallRealMethod().when(() -> BlobStoreConfig.hasSessionExpired(anyLong())); mContext = InstrumentationRegistry.getTargetContext(); mHandler = new TestHandler(Looper.getMainLooper()); @@ -236,7 +237,7 @@ public class BlobStoreManagerServiceTest { public void testHandleIdleMaintenance_deleteStaleSessions() throws Exception { // Setup sessions final File sessionFile1 = mock(File.class); - doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS + 1000) + doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MS + 1000) .when(sessionFile1).lastModified(); final long sessionId1 = 342; final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), @@ -256,7 +257,7 @@ public class BlobStoreManagerServiceTest { mUserSessions.append(sessionId2, session2); final File sessionFile3 = mock(File.class); - doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS - 2000) + doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MS - 2000) .when(sessionFile3).lastModified(); final long sessionId3 = 9484; final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(), diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java index 425c7247e50f..d45589d90ce3 100644 --- a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java +++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java @@ -288,7 +288,8 @@ public class OverrideValidatorImplTest { public void getOverrideAllowedState_finalBuildTargetSdkChangeDebugAppOptin_allowOverride() throws Exception { CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext) - .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 1).build(); + .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 1) + .addTargetSdkChangeWithId(TARGET_SDK, 2).build(); IOverrideValidator overrideValidator = config.getOverrideValidator(); when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) .thenReturn(ApplicationInfoBuilder.create() @@ -296,19 +297,23 @@ public class OverrideValidatorImplTest { .withTargetSdk(TARGET_SDK) .withPackageName(PACKAGE_NAME).build()); - OverrideAllowedState allowedState = + OverrideAllowedState stateTargetSdkGreaterChange = overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); + OverrideAllowedState stateTargetSdkEqualChange = + overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); - assertThat(allowedState) + assertThat(stateTargetSdkGreaterChange) .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER)); + + assertThat(stateTargetSdkEqualChange) + .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK)); } @Test public void getOverrideAllowedState_finalBuildTargetSdkChangeDebugAppOptout_rejectOverride() throws Exception { CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext) - .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1) - .addTargetSdkChangeWithId(TARGET_SDK, 2).build(); + .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1).build(); IOverrideValidator overrideValidator = config.getOverrideValidator(); when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt())) .thenReturn(ApplicationInfoBuilder.create() @@ -319,14 +324,10 @@ public class OverrideValidatorImplTest { OverrideAllowedState stateTargetSdkLessChange = overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME); - OverrideAllowedState stateTargetSdkEqualChange = - overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME); assertThat(stateTargetSdkLessChange).isEqualTo( new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK, TARGET_SDK_BEFORE)); - assertThat(stateTargetSdkEqualChange).isEqualTo( - new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK, TARGET_SDK)); } @Test diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java index 724048b1b8ee..4a774898e1b5 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java @@ -1997,19 +1997,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS = Sets.newSet( - UserManager.DISALLOW_CONFIG_DATE_TIME, - UserManager.DISALLOW_BLUETOOTH_SHARING, - UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, - UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, - UserManager.DISALLOW_CONFIG_PRIVATE_DNS, - UserManager.DISALLOW_CONFIG_TETHERING, - UserManager.DISALLOW_DATA_ROAMING, - UserManager.DISALLOW_SAFE_BOOT, - UserManager.DISALLOW_SMS, - UserManager.DISALLOW_USB_FILE_TRANSFER, UserManager.DISALLOW_AIRPLANE_MODE, - UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, - UserManager.DISALLOW_UNMUTE_MICROPHONE + UserManager.DISALLOW_CONFIG_DATE_TIME, + UserManager.DISALLOW_CONFIG_PRIVATE_DNS ); private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_LOCAL_RESTRICTIONS = @@ -2021,7 +2011,17 @@ public class DevicePolicyManagerTest extends DpmTestBase { UserManager.DISALLOW_CONTENT_SUGGESTIONS, UserManager.DISALLOW_DEBUGGING_FEATURES, UserManager.DISALLOW_SHARE_LOCATION, - UserManager.DISALLOW_OUTGOING_CALLS + UserManager.DISALLOW_OUTGOING_CALLS, + UserManager.DISALLOW_BLUETOOTH_SHARING, + UserManager.DISALLOW_CONFIG_CELL_BROADCASTS, + UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, + UserManager.DISALLOW_CONFIG_TETHERING, + UserManager.DISALLOW_DATA_ROAMING, + UserManager.DISALLOW_SAFE_BOOT, + UserManager.DISALLOW_SMS, + UserManager.DISALLOW_USB_FILE_TRANSFER, + UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, + UserManager.DISALLOW_UNMUTE_MICROPHONE ); public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception { @@ -2045,8 +2045,9 @@ public class DevicePolicyManagerTest extends DpmTestBase { parentDpm.setCameraDisabled(admin1, true); verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions( eq(CALLER_USER_HANDLE), - MockUtils.checkUserRestrictions(UserManager.DISALLOW_CAMERA), - MockUtils.checkUserRestrictions(CALLER_USER_HANDLE), + MockUtils.checkUserRestrictions(), + MockUtils.checkUserRestrictions(UserHandle.USER_SYSTEM, + UserManager.DISALLOW_CAMERA), eq(false)); DpmTestUtils.assertRestrictions( DpmTestUtils.newRestrictions(UserManager.DISALLOW_CAMERA), diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java index f35eecf05f32..b7692f912e39 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java @@ -44,9 +44,9 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @Test public void testUpdateOverlaysForUser() { final OverlayManagerServiceImpl impl = getImpl(); - installTargetPackage(TARGET, USER); - installTargetPackage("some.other.target", USER); - installOverlayPackage(OVERLAY, TARGET, USER); + addSystemPackage(target(TARGET), USER); + addSystemPackage(target("some.other.target"), USER);; + addSystemPackage(overlay(OVERLAY, TARGET), USER); // do nothing, expect no change final List<String> a = impl.updateOverlaysForUser(USER); @@ -54,8 +54,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI assertTrue(a.contains(TARGET)); // upgrade overlay, keep target - beginUpgradeOverlayPackage(OVERLAY, USER); - endUpgradeOverlayPackage(OVERLAY, TARGET, USER); + addSystemPackage(overlay(OVERLAY, TARGET), USER); final List<String> b = impl.updateOverlaysForUser(USER); assertEquals(1, b.size()); @@ -67,7 +66,7 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI assertTrue(c.contains(TARGET)); // upgrade overlay, switch to new target - addOverlayPackage(OVERLAY, "some.other.target", USER, true, false, 0); + addSystemPackage(overlay(OVERLAY, "some.other.target"), USER); final List<String> d = impl.updateOverlaysForUser(USER); assertEquals(2, d.size()); assertTrue(d.containsAll(Arrays.asList(TARGET, "some.other.target"))); @@ -81,23 +80,24 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @Test public void testImmutableEnabledChange() { final OverlayManagerServiceImpl impl = getImpl(); - installTargetPackage(TARGET, USER); + installNewPackage(target(TARGET), USER); + installNewPackage(overlay(OVERLAY, TARGET), USER); - addOverlayPackage(OVERLAY, TARGET, USER, false, false, 0); + configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */); impl.updateOverlaysForUser(USER); final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER); assertNotNull(o1); assertFalse(o1.isEnabled()); assertFalse(o1.isMutable); - addOverlayPackage(OVERLAY, TARGET, USER, false, true, 0); + configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */); impl.updateOverlaysForUser(USER); final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER); assertNotNull(o2); assertTrue(o2.isEnabled()); assertFalse(o2.isMutable); - addOverlayPackage(OVERLAY, TARGET, USER, false, false, 0); + configureSystemOverlay(OVERLAY, false /* mutable */, false /* enabled */, 0 /* priority */); impl.updateOverlaysForUser(USER); final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER); assertNotNull(o3); @@ -108,23 +108,24 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @Test public void testMutableEnabledChangeHasNoEffect() { final OverlayManagerServiceImpl impl = getImpl(); - installTargetPackage(TARGET, USER); + installNewPackage(target(TARGET), USER); + installNewPackage(overlay(OVERLAY, TARGET), USER); + configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */); - addOverlayPackage(OVERLAY, TARGET, USER, true, false, 0); impl.updateOverlaysForUser(USER); final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER); assertNotNull(o1); assertFalse(o1.isEnabled()); assertTrue(o1.isMutable); - addOverlayPackage(OVERLAY, TARGET, USER, true, true, 0); + configureSystemOverlay(OVERLAY, true /* mutable */, true /* enabled */, 0 /* priority */); impl.updateOverlaysForUser(USER); final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY, USER); assertNotNull(o2); assertFalse(o2.isEnabled()); assertTrue(o2.isMutable); - addOverlayPackage(OVERLAY, TARGET, USER, true, false, 0); + configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */); impl.updateOverlaysForUser(USER); final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER); assertNotNull(o3); @@ -135,15 +136,16 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @Test public void testMutableEnabledToImmutableEnabled() { final OverlayManagerServiceImpl impl = getImpl(); - installTargetPackage(TARGET, USER); + installNewPackage(target(TARGET), USER); + installNewPackage(overlay(OVERLAY, TARGET), USER); final BiConsumer<Boolean, Boolean> setOverlay = (mutable, enabled) -> { - addOverlayPackage(OVERLAY, TARGET, USER, mutable, enabled, 0); + configureSystemOverlay(OVERLAY, mutable, enabled, 0 /* priority */); impl.updateOverlaysForUser(USER); - final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER); - assertNotNull(o1); - assertEquals(enabled, o1.isEnabled()); - assertEquals(mutable, o1.isMutable); + final OverlayInfo o = impl.getOverlayInfo(OVERLAY, USER); + assertNotNull(o); + assertEquals(enabled, o.isEnabled()); + assertEquals(mutable, o.isMutable); }; // Immutable/enabled -> mutable/enabled @@ -178,70 +180,76 @@ public class OverlayManagerServiceImplRebootTests extends OverlayManagerServiceI @Test public void testMutablePriorityChange() { final OverlayManagerServiceImpl impl = getImpl(); - installTargetPackage(TARGET, USER); - addOverlayPackage(OVERLAY, TARGET, USER, true, true, 0); - addOverlayPackage(OVERLAY2, TARGET, USER, true, true, 1); + installNewPackage(target(TARGET), USER); + installNewPackage(overlay(OVERLAY, TARGET), USER); + installNewPackage(overlay(OVERLAY2, TARGET), USER); + configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 0 /* priority */); + configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 1 /* priority */); impl.updateOverlaysForUser(USER); final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER); assertNotNull(o1); assertEquals(0, o1.priority); + assertFalse(o1.isEnabled()); final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER); assertNotNull(o2); assertEquals(1, o2.priority); + assertFalse(o2.isEnabled()); // Overlay priority changing between reboots should not affect enable state of mutable - // overlays + // overlays. impl.setEnabled(OVERLAY, true, USER); // Reorder the overlays - addOverlayPackage(OVERLAY, TARGET, USER, true, true, 1); - addOverlayPackage(OVERLAY2, TARGET, USER, true, true, 0); + configureSystemOverlay(OVERLAY, true /* mutable */, false /* enabled */, 1 /* priority */); + configureSystemOverlay(OVERLAY2, true /* mutable */, false /* enabled */, 0 /* priority */); impl.updateOverlaysForUser(USER); final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER); assertNotNull(o3); assertEquals(1, o3.priority); + assertTrue(o3.isEnabled()); final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER); assertNotNull(o4); assertEquals(0, o4.priority); - assertTrue(o1.isEnabled()); + assertFalse(o4.isEnabled()); } @Test public void testImmutablePriorityChange() { final OverlayManagerServiceImpl impl = getImpl(); - installTargetPackage(TARGET, USER); - addOverlayPackage(OVERLAY, TARGET, USER, false, true, 0); - addOverlayPackage(OVERLAY2, TARGET, USER, false, true, 1); + installNewPackage(target(TARGET), USER); + installNewPackage(overlay(OVERLAY, TARGET), USER); + installNewPackage(overlay(OVERLAY2, TARGET), USER); + configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 0 /* priority */); + configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 1 /* priority */); impl.updateOverlaysForUser(USER); final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER); assertNotNull(o1); assertEquals(0, o1.priority); + assertTrue(o1.isEnabled()); final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER); assertNotNull(o2); assertEquals(1, o2.priority); - - // Overlay priority changing between reboots should not affect enable state of mutable - // overlays - impl.setEnabled(OVERLAY, true, USER); + assertTrue(o2.isEnabled()); // Reorder the overlays - addOverlayPackage(OVERLAY, TARGET, USER, false, true, 1); - addOverlayPackage(OVERLAY2, TARGET, USER, false, true, 0); + configureSystemOverlay(OVERLAY, false /* mutable */, true /* enabled */, 1 /* priority */); + configureSystemOverlay(OVERLAY2, false /* mutable */, true /* enabled */, 0 /* priority */); impl.updateOverlaysForUser(USER); final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY, USER); assertNotNull(o3); assertEquals(1, o3.priority); + assertTrue(o3.isEnabled()); final OverlayInfo o4 = impl.getOverlayInfo(OVERLAY2, USER); assertNotNull(o4); assertEquals(0, o4.priority); - assertTrue(o1.isEnabled()); + assertTrue(o4.isEnabled()); } } diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java index cd7343235750..b25af0543829 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java @@ -26,6 +26,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.content.om.OverlayInfo; +import android.os.OverlayablePolicy; import androidx.test.runner.AndroidJUnit4; @@ -49,11 +50,9 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes private static final String OVERLAY3 = OVERLAY + "3"; private static final int USER3 = USER2 + 1; - // tests: basics - @Test - public void testGetOverlayInfo() throws Exception { - installOverlayPackage(OVERLAY, TARGET, USER); + public void testGetOverlayInfo() { + installNewPackage(overlay(OVERLAY, TARGET), USER); final OverlayManagerServiceImpl impl = getImpl(); final OverlayInfo oi = impl.getOverlayInfo(OVERLAY, USER); @@ -64,10 +63,10 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testGetOverlayInfosForTarget() throws Exception { - installOverlayPackage(OVERLAY, TARGET, USER); - installOverlayPackage(OVERLAY2, TARGET, USER); - installOverlayPackage(OVERLAY3, TARGET, USER2); + public void testGetOverlayInfosForTarget() { + installNewPackage(overlay(OVERLAY, TARGET), USER); + installNewPackage(overlay(OVERLAY2, TARGET), USER); + installNewPackage(overlay(OVERLAY3, TARGET), USER2); final OverlayManagerServiceImpl impl = getImpl(); final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER); @@ -89,11 +88,11 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testGetOverlayInfosForUser() throws Exception { - installTargetPackage(TARGET, USER); - installOverlayPackage(OVERLAY, TARGET, USER); - installOverlayPackage(OVERLAY2, TARGET, USER); - installOverlayPackage(OVERLAY3, TARGET2, USER); + public void testGetOverlayInfosForUser() { + installNewPackage(target(TARGET), USER); + installNewPackage(overlay(OVERLAY, TARGET), USER); + installNewPackage(overlay(OVERLAY2, TARGET), USER); + installNewPackage(overlay(OVERLAY3, TARGET2), USER); final OverlayManagerServiceImpl impl = getImpl(); final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER); @@ -116,91 +115,86 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes } @Test - public void testPriority() throws Exception { - installOverlayPackage(OVERLAY, TARGET, USER); - installOverlayPackage(OVERLAY2, TARGET, USER); - installOverlayPackage(OVERLAY3, TARGET, USER); + public void testPriority() { + installNewPackage(overlay(OVERLAY, TARGET), USER); + installNewPackage(overlay(OVERLAY2, TARGET), USER); + installNewPackage(overlay(OVERLAY3, TARGET), USER); final OverlayManagerServiceImpl impl = getImpl(); final OverlayInfo o1 = impl.getOverlayInfo(OVERLAY, USER); final OverlayInfo o2 = impl.getOverlayInfo(OVERLAY2, USER); final OverlayInfo o3 = impl.getOverlayInfo(OVERLAY3, USER); - assertOverlayInfoList(TARGET, USER, o1, o2, o3); + assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3); assertTrue(impl.setLowestPriority(OVERLAY3, USER)); - assertOverlayInfoList(TARGET, USER, o3, o1, o2); + assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2); assertTrue(impl.setHighestPriority(OVERLAY3, USER)); - assertOverlayInfoList(TARGET, USER, o1, o2, o3); + assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3); assertTrue(impl.setPriority(OVERLAY, OVERLAY2, USER)); - assertOverlayInfoList(TARGET, USER, o2, o1, o3); + assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3); } @Test - public void testOverlayInfoStateTransitions() throws Exception { + public void testOverlayInfoStateTransitions() { final OverlayManagerServiceImpl impl = getImpl(); assertNull(impl.getOverlayInfo(OVERLAY, USER)); - installOverlayPackage(OVERLAY, TARGET, USER); + installNewPackage(overlay(OVERLAY, TARGET), USER); assertState(STATE_MISSING_TARGET, OVERLAY, USER); - installTargetPackage(TARGET, USER); + final DummyDeviceState.PackageBuilder target = target(TARGET); + installNewPackage(target, USER); assertState(STATE_DISABLED, OVERLAY, USER); impl.setEnabled(OVERLAY, true, USER); assertState(STATE_ENABLED, OVERLAY, USER); // target upgrades do not change the state of the overlay - beginUpgradeTargetPackage(TARGET, USER); - assertState(STATE_ENABLED, OVERLAY, USER); - - endUpgradeTargetPackage(TARGET, USER); + upgradePackage(target, USER); assertState(STATE_ENABLED, OVERLAY, USER); - uninstallTargetPackage(TARGET, USER); + uninstallPackage(TARGET, USER); assertState(STATE_MISSING_TARGET, OVERLAY, USER); - installTargetPackage(TARGET, USER); + installNewPackage(target, USER); assertState(STATE_ENABLED, OVERLAY, USER); } @Test - public void testOnOverlayPackageUpgraded() throws Exception { - final OverlayManagerServiceImpl impl = getImpl(); + public void testOnOverlayPackageUpgraded() { final DummyListener listener = getListener(); - installTargetPackage(TARGET, USER); - installOverlayPackage(OVERLAY, TARGET, USER); - impl.onOverlayPackageReplacing(OVERLAY, USER); + final DummyDeviceState.PackageBuilder target = target(TARGET); + final DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET); + installNewPackage(target, USER); + installNewPackage(overlay, USER); listener.count = 0; - impl.onOverlayPackageReplaced(OVERLAY, USER); - assertEquals(1, listener.count); + upgradePackage(overlay, USER); + assertEquals(2, listener.count); // upgrade to a version where the overlay has changed its target - beginUpgradeOverlayPackage(OVERLAY, USER); - listener.count = 0; - endUpgradeOverlayPackage(OVERLAY, "some.other.target", USER); // expect once for the old target package, once for the new target package - assertEquals(2, listener.count); + listener.count = 0; + final DummyDeviceState.PackageBuilder overlay2 = overlay(OVERLAY, "some.other.target"); + upgradePackage(overlay2, USER); + assertEquals(3, listener.count); - beginUpgradeOverlayPackage(OVERLAY, USER); listener.count = 0; - endUpgradeOverlayPackage(OVERLAY, "some.other.target", USER); - assertEquals(1, listener.count); + upgradePackage(overlay2, USER); + assertEquals(2, listener.count); } - // tests: listener interface - @Test - public void testListener() throws Exception { + public void testListener() { final OverlayManagerServiceImpl impl = getImpl(); final DummyListener listener = getListener(); - installOverlayPackage(OVERLAY, TARGET, USER); + installNewPackage(overlay(OVERLAY, TARGET), USER); assertEquals(1, listener.count); listener.count = 0; - installTargetPackage(TARGET, USER); + installNewPackage(target(TARGET), USER); assertEquals(1, listener.count); listener.count = 0; @@ -211,4 +205,49 @@ public class OverlayManagerServiceImplTests extends OverlayManagerServiceImplTes impl.setEnabled(OVERLAY, true, USER); assertEquals(0, listener.count); } + + @Test + public void testConfigurator() { + final DummyPackageManagerHelper packageManager = getPackageManager(); + packageManager.overlayableConfigurator = "actor"; + packageManager.overlayableConfiguratorTargets = new String[]{TARGET}; + reinitializeImpl(); + + installNewPackage(target("actor").setCertificate("one"), USER); + installNewPackage(target(TARGET) + .addOverlayable("TestResources") + .setCertificate("two"), USER); + + final DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources") + .setCertificate("one"); + installNewPackage(overlay, USER); + + final DummyIdmapDaemon idmapDaemon = getIdmapDaemon(); + final DummyIdmapDaemon.IdmapHeader idmap = idmapDaemon.getIdmap(overlay.build().apkPath); + assertNotNull(idmap); + assertEquals(OverlayablePolicy.ACTOR_SIGNATURE, + idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE); + } + + @Test + public void testConfiguratorDifferentSignatures() { + final DummyPackageManagerHelper packageManager = getPackageManager(); + packageManager.overlayableConfigurator = "actor"; + packageManager.overlayableConfiguratorTargets = new String[]{TARGET}; + reinitializeImpl(); + + installNewPackage(target("actor").setCertificate("one"), USER); + installNewPackage(target(TARGET) + .addOverlayable("TestResources") + .setCertificate("two"), USER); + + final DummyDeviceState.PackageBuilder overlay = overlay(OVERLAY, TARGET, "TestResources") + .setCertificate("two"); + installNewPackage(overlay, USER); + + final DummyIdmapDaemon idmapDaemon = getIdmapDaemon(); + final DummyIdmapDaemon.IdmapHeader idmap = idmapDaemon.getIdmap(overlay.build().apkPath); + assertNotNull(idmap); + assertEquals(0, idmap.policies & OverlayablePolicy.ACTOR_SIGNATURE); + } } diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java index 9eda718ed922..ec6a48182a25 100644 --- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java @@ -17,6 +17,7 @@ package com.android.server.om; import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -26,17 +27,19 @@ import android.content.om.OverlayInfo.State; import android.content.om.OverlayableInfo; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; +import android.util.ArrayMap; import android.util.ArraySet; import androidx.annotation.Nullable; import com.android.internal.content.om.OverlayConfig; +import org.junit.Assert; import org.junit.Before; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -47,29 +50,48 @@ class OverlayManagerServiceImplTestsBase { private OverlayManagerServiceImpl mImpl; private DummyDeviceState mState; private DummyListener mListener; + private DummyPackageManagerHelper mPackageManager; + private DummyIdmapDaemon mIdmapDaemon; + private OverlayConfig mOverlayConfig; @Before public void setUp() { mState = new DummyDeviceState(); mListener = new DummyListener(); - final DummyPackageManagerHelper pmh = new DummyPackageManagerHelper(mState); + mPackageManager = new DummyPackageManagerHelper(mState); + mIdmapDaemon = new DummyIdmapDaemon(mState); + mOverlayConfig = mock(OverlayConfig.class); + when(mOverlayConfig.getPriority(any())).thenReturn(OverlayConfig.DEFAULT_PRIORITY); + when(mOverlayConfig.isEnabled(any())).thenReturn(false); + when(mOverlayConfig.isMutable(any())).thenReturn(true); + reinitializeImpl(); + } - mImpl = new OverlayManagerServiceImpl(pmh, - new DummyIdmapManager(mState, pmh), + void reinitializeImpl() { + mImpl = new OverlayManagerServiceImpl(mPackageManager, + new IdmapManager(mIdmapDaemon, mPackageManager), new OverlayManagerSettings(), - mState.mOverlayConfig, + mOverlayConfig, new String[0], mListener); } - public OverlayManagerServiceImpl getImpl() { + OverlayManagerServiceImpl getImpl() { return mImpl; } - public DummyListener getListener() { + DummyListener getListener() { return mListener; } + DummyPackageManagerHelper getPackageManager() { + return mPackageManager; + } + + DummyIdmapDaemon getIdmapDaemon() { + return mIdmapDaemon; + } + void assertState(@State int expected, final String overlayPackageName, int userId) { final OverlayInfo info = mImpl.getOverlayInfo(overlayPackageName, userId); if (info == null) { @@ -81,7 +103,7 @@ class OverlayManagerServiceImplTestsBase { assertEquals(msg, expected, info.state); } - void assertOverlayInfoList(final String targetPackageName, int userId, + void assertOverlayInfoForTarget(final String targetPackageName, int userId, OverlayInfo... overlayInfos) { final List<OverlayInfo> expected = mImpl.getOverlayInfosForTarget(targetPackageName, userId); @@ -89,198 +111,202 @@ class OverlayManagerServiceImplTestsBase { assertEquals(expected, actual); } - /** - * Creates an overlay configured through {@link OverlayConfig}. - * - * @throws IllegalStateException if the package is already installed - */ - void addOverlayPackage(String packageName, String targetPackageName, int userId, - boolean mutable, boolean enabled, int priority) { - mState.addOverlay(packageName, targetPackageName, userId, mutable, enabled, priority); + DummyDeviceState.PackageBuilder target(String packageName) { + return new DummyDeviceState.PackageBuilder(packageName, null /* targetPackageName */, + null /* targetOverlayableName */); } - /** - * Adds the target package to the device. - * - * This corresponds to when the OMS receives the - * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast. - * - * @throws IllegalStateException if the package is not currently installed - */ - void installTargetPackage(String packageName, int userId) { - if (mState.select(packageName, userId) != null) { - throw new IllegalStateException("package already installed"); - } - mState.addTarget(packageName, userId); - mImpl.onTargetPackageAdded(packageName, userId); + DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName) { + return overlay(packageName, targetPackageName, null /* targetOverlayableName */); } - /** - * Begins upgrading the target package. - * - * This corresponds to when the OMS receives the - * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the - * {@link android.content.Intent#EXTRA_REPLACING} extra. - * - * @throws IllegalStateException if the package is not currently installed - */ - void beginUpgradeTargetPackage(String packageName, int userId) { - if (mState.select(packageName, userId) == null) { - throw new IllegalStateException("package not installed"); - } - mImpl.onTargetPackageReplacing(packageName, userId); + DummyDeviceState.PackageBuilder overlay(String packageName, String targetPackageName, + String targetOverlayableName) { + return new DummyDeviceState.PackageBuilder(packageName, targetPackageName, + targetOverlayableName); } - /** - * Ends upgrading the target package. - * - * This corresponds to when the OMS receives the - * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the - * {@link android.content.Intent#EXTRA_REPLACING} extra. - * - * @throws IllegalStateException if the package is not currently installed - */ - void endUpgradeTargetPackage(String packageName, int userId) { - if (mState.select(packageName, userId) == null) { - throw new IllegalStateException("package not installed"); - } - mState.addTarget(packageName, userId); - mImpl.onTargetPackageReplaced(packageName, userId); + void addSystemPackage(DummyDeviceState.PackageBuilder pkg, int userId) { + mState.add(pkg, userId); } - /** - * Removes the target package from the device. - * - * This corresponds to when the OMS receives the - * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast. - * - * @throws IllegalStateException if the package is not currently installed - */ - void uninstallTargetPackage(String packageName, int userId) { - if (mState.select(packageName, userId) == null) { - throw new IllegalStateException("package not installed"); - } - mState.remove(packageName, userId); - mImpl.onTargetPackageRemoved(packageName, userId); + void configureSystemOverlay(String packageName, boolean mutable, boolean enabled, + int priority) { + when(mOverlayConfig.getPriority(packageName)).thenReturn(priority); + when(mOverlayConfig.isEnabled(packageName)).thenReturn(enabled); + when(mOverlayConfig.isMutable(packageName)).thenReturn(mutable); } /** - * Adds the overlay package to the device. + * Adds the package to the device. * * This corresponds to when the OMS receives the * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast. * - * @throws IllegalStateException if the package is already installed + * @throws IllegalStateException if the package is currently installed */ - void installOverlayPackage(String packageName, String targetPackageName, int userId) { - if (mState.select(packageName, userId) != null) { - throw new IllegalStateException("package already installed"); + void installNewPackage(DummyDeviceState.PackageBuilder pkg, int userId) { + if (mState.select(pkg.packageName, userId) != null) { + throw new IllegalStateException("package " + pkg.packageName + " already installed"); + } + mState.add(pkg, userId); + if (pkg.targetPackage == null) { + mImpl.onTargetPackageAdded(pkg.packageName, userId); + } else { + mImpl.onOverlayPackageAdded(pkg.packageName, userId); } - mState.addOverlay(packageName, targetPackageName, userId); - mImpl.onOverlayPackageAdded(packageName, userId); } /** - * Begins upgrading the overlay package. + * Begins upgrading the package. * * This corresponds to when the OMS receives the * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast with the + * {@link android.content.Intent#EXTRA_REPLACING} extra and then receives the + * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the * {@link android.content.Intent#EXTRA_REPLACING} extra. * * @throws IllegalStateException if the package is not currently installed */ - void beginUpgradeOverlayPackage(String packageName, int userId) { - if (mState.select(packageName, userId) == null) { - throw new IllegalStateException("package not installed, cannot upgrade"); + void upgradePackage(DummyDeviceState.PackageBuilder pkg, int userId) { + final DummyDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId); + if (replacedPackage == null) { + throw new IllegalStateException("package " + pkg.packageName + " not installed"); + } + if (replacedPackage.targetPackageName != null) { + mImpl.onOverlayPackageReplacing(pkg.packageName, userId); } - mImpl.onOverlayPackageReplacing(packageName, userId); + mState.add(pkg, userId); + if (pkg.targetPackage == null) { + mImpl.onTargetPackageReplaced(pkg.packageName, userId); + } else { + mImpl.onOverlayPackageReplaced(pkg.packageName, userId); + } } /** - * Ends upgrading the overlay package, potentially changing its target package. + * Removes the package from the device. * * This corresponds to when the OMS receives the - * {@link android.content.Intent#ACTION_PACKAGE_ADDED} broadcast with the - * {@link android.content.Intent#EXTRA_REPLACING} extra. + * {@link android.content.Intent#ACTION_PACKAGE_REMOVED} broadcast. * * @throws IllegalStateException if the package is not currently installed */ - void endUpgradeOverlayPackage(String packageName, String targetPackageName, int userId) { - if (mState.select(packageName, userId) == null) { - throw new IllegalStateException("package not installed, cannot upgrade"); + void uninstallPackage(String packageName, int userId) { + final DummyDeviceState.Package pkg = mState.select(packageName, userId); + if (pkg == null) { + throw new IllegalStateException("package " + packageName+ " not installed"); + } + mState.remove(pkg.packageName); + if (pkg.targetPackageName == null) { + mImpl.onTargetPackageRemoved(pkg.packageName, userId); + } else { + mImpl.onOverlayPackageRemoved(pkg.packageName, userId); } - - mState.addOverlay(packageName, targetPackageName, userId); - mImpl.onOverlayPackageReplaced(packageName, userId); } - private static final class DummyDeviceState { - private List<Package> mPackages = new ArrayList<>(); - private OverlayConfig mOverlayConfig = mock(OverlayConfig.class); - - /** Adds a non-overlay to the device. */ - public void addTarget(String packageName, int userId) { - remove(packageName, userId); - mPackages.add(new Package(packageName, userId, null, false, false, 0)); - } - - /** Adds an overlay to the device. */ - public void addOverlay(String packageName, String targetPackageName, int userId) { - addOverlay(packageName, targetPackageName, userId, true, false, OverlayConfig.DEFAULT_PRIORITY); - } - - /** Adds a configured overlay to the device. */ - public void addOverlay(String packageName, String targetPackageName, int userId, - boolean mutable, boolean enabled, int priority) { - remove(packageName, userId); - mPackages.add(new Package(packageName, userId, targetPackageName, mutable, enabled, - priority)); - when(mOverlayConfig.getPriority(packageName)).thenReturn(priority); - when(mOverlayConfig.isEnabled(packageName)).thenReturn(enabled); - when(mOverlayConfig.isMutable(packageName)).thenReturn(mutable); - } - - /** Remove a package from the device. */ - public void remove(String packageName, int userId) { - final Iterator<Package> iter = mPackages.iterator(); - while (iter.hasNext()) { - final Package pkg = iter.next(); - if (pkg.packageName.equals(packageName) && pkg.userId == userId) { - iter.remove(); - return; - } + /** Represents the state of packages installed on a fake device. */ + static class DummyDeviceState { + private ArrayMap<String, Package> mPackages = new ArrayMap<>(); + + void add(PackageBuilder pkgBuilder, int userId) { + final Package pkg = pkgBuilder.build(); + final Package previousPkg = select(pkg.packageName, userId); + mPackages.put(pkg.packageName, pkg); + + pkg.installedUserIds.add(userId); + if (previousPkg != null) { + pkg.installedUserIds.addAll(previousPkg.installedUserIds); } } - /** Retrieves all packages on device for a particular user. */ - public List<Package> select(int userId) { - return mPackages.stream().filter(p -> p.userId == userId).collect(Collectors.toList()); + void remove(String packageName) { + mPackages.remove(packageName); + } + + void uninstall(String packageName, int userId) { + final Package pkg = mPackages.get(packageName); + if (pkg != null) { + pkg.installedUserIds.remove(userId); + } + } + + List<Package> select(int userId) { + return mPackages.values().stream().filter(p -> p.installedUserIds.contains(userId)) + .collect(Collectors.toList()); + } + + Package select(String packageName, int userId) { + final Package pkg = mPackages.get(packageName); + return pkg != null && pkg.installedUserIds.contains(userId) ? pkg : null; } - /** Retrieves the package with the specified package name for a particular user. */ - public Package select(String packageName, int userId) { - return mPackages.stream().filter( - p -> p.packageName.equals(packageName) && p.userId == userId) - .findFirst().orElse(null); + private Package selectFromPath(String path) { + return mPackages.values().stream() + .filter(p -> p.apkPath.equals(path)).findFirst().orElse(null); } - private static final class Package { - public final String packageName; - public final int userId; - public final String targetPackageName; - public final boolean mutable; - public final boolean enabled; - public final int priority; + static final class PackageBuilder { + private String packageName; + private String targetPackage; + private String certificate = "[default]"; + private int version = 0; + private ArrayList<String> overlayableNames = new ArrayList<>(); + private String targetOverlayableName; - private Package(String packageName, int userId, String targetPackageName, - boolean mutable, boolean enabled, int priority) { + private PackageBuilder(String packageName, String targetPackage, + String targetOverlayableName) { + this.packageName = packageName; + this.targetPackage = targetPackage; + this.targetOverlayableName = targetOverlayableName; + } + + PackageBuilder setCertificate(String certificate) { + this.certificate = certificate; + return this; + } + + PackageBuilder addOverlayable(String overlayableName) { + overlayableNames.add(overlayableName); + return this; + } + + PackageBuilder setVersion(int version) { + this.version = version; + return this; + } + + Package build() { + final String apkPath = String.format("%s/%s/base.apk", + targetPackage == null ? "/system/app/:" : "/vendor/overlay/:", + packageName); + final Package newPackage = new Package(packageName, targetPackage, + targetOverlayableName, version, apkPath, certificate); + newPackage.overlayableNames.addAll(overlayableNames); + return newPackage; + } + } + + static final class Package { + final String packageName; + final String targetPackageName; + final String targetOverlayableName; + final int versionCode; + final String apkPath; + final String certificate; + final ArrayList<String> overlayableNames = new ArrayList<>(); + private final ArraySet<Integer> installedUserIds = new ArraySet<>(); + + private Package(String packageName, String targetPackageName, + String targetOverlayableName, int versionCode, String apkPath, + String certificate) { this.packageName = packageName; - this.userId = userId; this.targetPackageName = targetPackageName; - this.mutable = mutable; - this.enabled = enabled; - this.priority = priority; + this.targetOverlayableName = targetOverlayableName; + this.versionCode = versionCode; + this.apkPath = apkPath; + this.certificate = certificate; } } } @@ -288,6 +314,8 @@ class OverlayManagerServiceImplTestsBase { static final class DummyPackageManagerHelper implements PackageManagerHelper, OverlayableInfoCallback { private final DummyDeviceState mState; + String[] overlayableConfiguratorTargets = new String[0]; + String overlayableConfigurator = ""; private DummyPackageManagerHelper(DummyDeviceState state) { mState = state; @@ -300,13 +328,12 @@ class OverlayManagerServiceImplTestsBase { return null; } final ApplicationInfo ai = new ApplicationInfo(); - ai.sourceDir = String.format("%s/%s/base.apk", - pkg.targetPackageName == null ? "/system/app/" : "/vendor/overlay/", - pkg.packageName); + ai.sourceDir = pkg.apkPath; PackageInfo pi = new PackageInfo(); pi.applicationInfo = ai; pi.packageName = pkg.packageName; pi.overlayTarget = pkg.targetPackageName; + pi.targetOverlayableName = pkg.targetOverlayableName; pi.overlayCategory = "dummy-category-" + pkg.targetPackageName; return pi; } @@ -314,14 +341,16 @@ class OverlayManagerServiceImplTestsBase { @Override public boolean signaturesMatching(@NonNull String packageName1, @NonNull String packageName2, int userId) { - return false; + final DummyDeviceState.Package pkg1 = mState.select(packageName1, userId); + final DummyDeviceState.Package pkg2 = mState.select(packageName2, userId); + return pkg1 != null && pkg2 != null && pkg1.certificate.equals(pkg2.certificate); } @Override public List<PackageInfo> getOverlayPackages(int userId) { return mState.select(userId).stream() .filter(p -> p.targetPackageName != null) - .map(p -> getPackageInfo(p.packageName, p.userId)) + .map(p -> getPackageInfo(p.packageName, userId)) .collect(Collectors.toList()); } @@ -329,7 +358,11 @@ class OverlayManagerServiceImplTestsBase { @Override public OverlayableInfo getOverlayableForTarget(@NonNull String packageName, @NonNull String targetOverlayableName, int userId) { - throw new UnsupportedOperationException(); + final DummyDeviceState.Package pkg = mState.select(packageName, userId); + if (pkg == null || !pkg.overlayableNames.contains(targetOverlayableName)) { + return null; + } + return new OverlayableInfo(targetOverlayableName, null /* actor */); } @Nullable @@ -341,69 +374,98 @@ class OverlayManagerServiceImplTestsBase { @NonNull @Override public Map<String, Map<String, String>> getNamedActors() { - throw new UnsupportedOperationException(); + return Collections.emptyMap(); } @Override public boolean doesTargetDefineOverlayable(String targetPackageName, int userId) { - throw new UnsupportedOperationException(); + final DummyDeviceState.Package pkg = mState.select(targetPackageName, userId); + return pkg != null && pkg.overlayableNames.contains(targetPackageName); } @Override public void enforcePermission(String permission, String message) throws SecurityException { throw new UnsupportedOperationException(); } + + @Override + public String[] getOverlayableConfiguratorTargets() { + return overlayableConfiguratorTargets; + } + + @Override + public String getOverlayableConfigurator() { + return overlayableConfigurator; + } } - static class DummyIdmapManager extends IdmapManager { + static class DummyIdmapDaemon extends IdmapDaemon { private final DummyDeviceState mState; - private Set<String> mIdmapFiles = new ArraySet<>(); + private final ArrayMap<String, IdmapHeader> mIdmapFiles = new ArrayMap<>(); - private DummyIdmapManager(DummyDeviceState state, - DummyPackageManagerHelper packageManagerHelper) { - super(packageManagerHelper); - mState = state; + DummyIdmapDaemon(DummyDeviceState state) { + this.mState = state; + } + + private int getCrc(@NonNull final String path) { + final DummyDeviceState.Package pkg = mState.selectFromPath(path); + Assert.assertNotNull(pkg); + return pkg.versionCode; } @Override - boolean createIdmap(@NonNull final PackageInfo targetPackage, - @NonNull final PackageInfo overlayPackage, int userId) { - final DummyDeviceState.Package t = mState.select(targetPackage.packageName, userId); - if (t == null) { - return false; - } - final DummyDeviceState.Package o = mState.select(overlayPackage.packageName, userId); - if (o == null) { - return false; - } - final String key = createKey(overlayPackage.packageName, userId); - return mIdmapFiles.add(key); + String createIdmap(String targetPath, String overlayPath, int policies, boolean enforce, + int userId) { + mIdmapFiles.put(overlayPath, new IdmapHeader(getCrc(targetPath), + getCrc(overlayPath), targetPath, policies, enforce)); + return overlayPath; + } + + @Override + boolean removeIdmap(String overlayPath, int userId) { + return mIdmapFiles.remove(overlayPath) != null; } @Override - boolean removeIdmap(@NonNull final OverlayInfo oi, final int userId) { - final String key = createKey(oi.packageName, oi.userId); - if (!mIdmapFiles.contains(key)) { + boolean verifyIdmap(String targetPath, String overlayPath, int policies, boolean enforce, + int userId) { + final IdmapHeader idmap = mIdmapFiles.get(overlayPath); + if (idmap == null) { return false; } - mIdmapFiles.remove(key); - return true; + return idmap.isUpToDate(getCrc(targetPath), getCrc(overlayPath), targetPath); } @Override - boolean idmapExists(@NonNull final OverlayInfo oi) { - final String key = createKey(oi.packageName, oi.userId); - return mIdmapFiles.contains(key); + boolean idmapExists(String overlayPath, int userId) { + return mIdmapFiles.containsKey(overlayPath); } - @Override - boolean idmapExists(@NonNull final PackageInfo overlayPackage, final int userId) { - final String key = createKey(overlayPackage.packageName, userId); - return mIdmapFiles.contains(key); + IdmapHeader getIdmap(String overlayPath) { + return mIdmapFiles.get(overlayPath); } - private String createKey(@NonNull final String packageName, final int userId) { - return String.format("%s:%d", packageName, userId); + static class IdmapHeader { + private final int targetCrc; + private final int overlayCrc; + final String targetPath; + final int policies; + final boolean enforceOverlayable; + + private IdmapHeader(int targetCrc, int overlayCrc, String targetPath, int policies, + boolean enforceOverlayable) { + this.targetCrc = targetCrc; + this.overlayCrc = overlayCrc; + this.targetPath = targetPath; + this.policies = policies; + this.enforceOverlayable = enforceOverlayable; + } + + private boolean isUpToDate(int expectedTargetCrc, int expectedOverlayCrc, + String expectedTargetPath) { + return expectedTargetCrc == targetCrc && expectedOverlayCrc == overlayCrc + && expectedTargetPath.equals(targetPath); + } } } 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/BubbleExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java index 38b71b707196..13457f0a284c 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/BubbleExtractorTest.java @@ -26,6 +26,8 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; import static junit.framework.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -166,6 +168,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, ALLOW_BUBBLE_OFF /* channel */); + when(mActivityManager.isLowRamDevice()).thenReturn(false); + setUpShortcutBubble(true /* isValid */); NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); @@ -178,6 +182,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, DEFAULT_ALLOW_BUBBLE /* channel */); + when(mActivityManager.isLowRamDevice()).thenReturn(false); + setUpShortcutBubble(true /* isValid */); NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); @@ -190,6 +196,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_ALL /* app */, ALLOW_BUBBLE_ON /* channel */); + when(mActivityManager.isLowRamDevice()).thenReturn(false); + setUpShortcutBubble(true /* isValid */); NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); @@ -202,6 +210,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { setUpBubblesEnabled(false /* feature */, BUBBLE_PREFERENCE_ALL /* app */, ALLOW_BUBBLE_ON /* channel */); + when(mActivityManager.isLowRamDevice()).thenReturn(false); + setUpShortcutBubble(true /* isValid */); NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); @@ -215,6 +225,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_NONE /* app */, ALLOW_BUBBLE_ON /* channel */); + when(mActivityManager.isLowRamDevice()).thenReturn(false); + setUpShortcutBubble(true /* isValid */); NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); @@ -228,6 +240,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_NONE /* app */, DEFAULT_ALLOW_BUBBLE /* channel */); + when(mActivityManager.isLowRamDevice()).thenReturn(false); + setUpShortcutBubble(true /* isValid */); NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); @@ -241,6 +255,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_SELECTED /* app */, DEFAULT_ALLOW_BUBBLE /* channel */); + when(mActivityManager.isLowRamDevice()).thenReturn(false); + setUpShortcutBubble(true /* isValid */); NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); @@ -254,6 +270,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_SELECTED /* app */, ALLOW_BUBBLE_OFF /* channel */); + when(mActivityManager.isLowRamDevice()).thenReturn(false); + setUpShortcutBubble(true /* isValid */); NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); @@ -267,6 +285,9 @@ public class BubbleExtractorTest extends UiServiceTestCase { setUpBubblesEnabled(true /* feature */, BUBBLE_PREFERENCE_SELECTED /* app */, ALLOW_BUBBLE_ON /* channel */); + when(mActivityManager.isLowRamDevice()).thenReturn(false); + setUpShortcutBubble(true /* isValid */); + NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); @@ -279,6 +300,9 @@ public class BubbleExtractorTest extends UiServiceTestCase { setUpBubblesEnabled(false /* feature */, BUBBLE_PREFERENCE_SELECTED /* app */, ALLOW_BUBBLE_ON /* channel */); + when(mActivityManager.isLowRamDevice()).thenReturn(false); + setUpShortcutBubble(true /* isValid */); + NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); @@ -305,6 +329,7 @@ public class BubbleExtractorTest extends UiServiceTestCase { mBubbleExtractor.process(r); assertTrue(r.canBubble()); + assertNotNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } @@ -320,6 +345,7 @@ public class BubbleExtractorTest extends UiServiceTestCase { mBubbleExtractor.process(r); assertTrue(r.canBubble()); + assertNotNull(r.getNotification().getBubbleMetadata()); assertTrue(r.getNotification().isBubbleNotification()); } @@ -335,6 +361,7 @@ public class BubbleExtractorTest extends UiServiceTestCase { mBubbleExtractor.process(r); assertTrue(r.canBubble()); + assertNotNull(r.getNotification().getBubbleMetadata()); assertTrue(r.getNotification().isBubbleNotification()); } @@ -350,7 +377,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { r.setShortcutInfo(null); mBubbleExtractor.process(r); - assertTrue(r.canBubble()); + assertFalse(r.canBubble()); + assertNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } @@ -366,7 +394,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { r.setShortcutInfo(null); mBubbleExtractor.process(r); - assertTrue(r.canBubble()); + assertFalse(r.canBubble()); + assertNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } @@ -381,7 +410,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); - assertTrue(r.canBubble()); + assertFalse(r.canBubble()); + assertNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } @@ -395,7 +425,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { NotificationRecord r = getNotificationRecord(false /* bubble */); mBubbleExtractor.process(r); - assertTrue(r.canBubble()); + assertFalse(r.canBubble()); + assertNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } @@ -414,7 +445,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { mBubbleExtractor.process(r); - assertTrue(r.canBubble()); + assertFalse(r.canBubble()); + assertNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } @@ -429,7 +461,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); - assertTrue(r.canBubble()); + assertFalse(r.canBubble()); + assertNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } @@ -445,7 +478,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); - assertTrue(r.canBubble()); + assertFalse(r.canBubble()); + assertNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } @@ -462,7 +496,8 @@ public class BubbleExtractorTest extends UiServiceTestCase { NotificationRecord r = getNotificationRecord(true /* bubble */); mBubbleExtractor.process(r); - assertTrue(r.canBubble()); + assertFalse(r.canBubble()); + assertNull(r.getNotification().getBubbleMetadata()); assertFalse(r.getNotification().isBubbleNotification()); } } 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 ced780475fb7..cf636823d5f7 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -6135,8 +6135,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { "tag", mUid, 0, nb.build(), new UserHandle(mUid), null, 0); NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel); - - // Test: Send the bubble notification mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); @@ -6154,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); @@ -6168,12 +6166,12 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { verify(mLauncherApps, times(1)).unregisterCallback(launcherAppsCallback.getValue()); // We're no longer a bubble - Notification notif2 = mService.getNotificationRecord( - nr.getSbn().getKey()).getNotification(); - assertFalse(notif2.isBubbleNotification()); + NotificationRecord notif2 = mService.getNotificationRecord( + nr.getSbn().getKey()); + assertNull(notif2.getShortcutInfo()); + assertFalse(notif2.getNotification().isBubbleNotification()); } - @Test public void testNotificationBubbles_shortcut_stopListeningWhenNotifRemoved() throws RemoteException { @@ -6227,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/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java index eb2d9fed197f..c700a090fa2e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java @@ -48,6 +48,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @SmallTest @@ -73,6 +74,8 @@ public class ShortcutHelperTest extends UiServiceTestCase { StatusBarNotification mSbn; @Mock Notification.BubbleMetadata mBubbleMetadata; + @Mock + ShortcutInfo mShortcutInfo; ShortcutHelper mShortcutHelper; @@ -86,13 +89,13 @@ public class ShortcutHelperTest extends UiServiceTestCase { when(mNr.getSbn()).thenReturn(mSbn); when(mSbn.getPackageName()).thenReturn(PKG); when(mNr.getNotification()).thenReturn(mNotif); + when(mNr.getShortcutInfo()).thenReturn(mShortcutInfo); + when(mShortcutInfo.getId()).thenReturn(SHORTCUT_ID); when(mNotif.getBubbleMetadata()).thenReturn(mBubbleMetadata); when(mBubbleMetadata.getShortcutId()).thenReturn(SHORTCUT_ID); } private LauncherApps.Callback addShortcutBubbleAndVerifyListener() { - when(mNotif.isBubbleNotification()).thenReturn(true); - mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr, false /* removed */, null /* handler */); @@ -124,12 +127,40 @@ public class ShortcutHelperTest extends UiServiceTestCase { } @Test - public void testBubbleNoLongerBubble_listenerRemoved() { + public void testBubbleNoLongerHasBubbleMetadata_listenerRemoved() { // First set it up to listen addShortcutBubbleAndVerifyListener(); // Then make it not a bubble - when(mNotif.isBubbleNotification()).thenReturn(false); + when(mNotif.getBubbleMetadata()).thenReturn(null); + mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr, + false /* removed */, + null /* handler */); + + verify(mLauncherApps, times(1)).unregisterCallback(any()); + } + + @Test + public void testBubbleNoLongerHasShortcutId_listenerRemoved() { + // First set it up to listen + addShortcutBubbleAndVerifyListener(); + + // Clear out shortcutId + when(mBubbleMetadata.getShortcutId()).thenReturn(null); + mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr, + false /* removed */, + null /* handler */); + + verify(mLauncherApps, times(1)).unregisterCallback(any()); + } + + @Test + public void testNotifNoLongerHasShortcut_listenerRemoved() { + // First set it up to listen + addShortcutBubbleAndVerifyListener(); + + // Clear out shortcutId + when(mNr.getShortcutInfo()).thenReturn(null); mShortcutHelper.maybeListenForShortcutChangesForBubbles(mNr, false /* removed */, null /* handler */); @@ -138,6 +169,17 @@ public class ShortcutHelperTest extends UiServiceTestCase { } @Test + public void testOnShortcutsChanged_listenerRemoved() { + // First set it up to listen + LauncherApps.Callback callback = addShortcutBubbleAndVerifyListener(); + + // App shortcuts are removed: + callback.onShortcutsChanged(PKG, Collections.emptyList(), mock(UserHandle.class)); + + verify(mLauncherApps, times(1)).unregisterCallback(any()); + } + + @Test public void testListenerNotifiedOnShortcutRemoved() { LauncherApps.Callback callback = addShortcutBubbleAndVerifyListener(); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 4e82ceb882a8..d063f10d52e2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -57,6 +57,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.same; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_FIXED_TRANSFORM; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; @@ -1060,6 +1061,11 @@ public class DisplayContentTests extends WindowTestsBase { @Test public void testApplyTopFixedRotationTransform() { mWm.mIsFixedRotationTransformEnabled = true; + final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); + // Only non-movable (gesture) navigation bar will be animated by fixed rotation animation. + doReturn(false).when(displayPolicy).navigationBarCanMove(); + displayPolicy.addWindowLw(mStatusBarWindow, mStatusBarWindow.mAttrs); + displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs); final Configuration config90 = new Configuration(); mDisplayContent.computeScreenConfiguration(config90, ROTATION_90); @@ -1080,6 +1086,12 @@ public class DisplayContentTests extends WindowTestsBase { ROTATION_0 /* oldRotation */, ROTATION_90 /* newRotation */, false /* forceUpdate */)); + assertNotNull(mDisplayContent.getFixedRotationAnimationController()); + assertTrue(mStatusBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS, + ANIMATION_TYPE_FIXED_TRANSFORM)); + assertTrue(mNavBarWindow.getParent().isAnimating(WindowContainer.AnimationFlags.PARENTS, + ANIMATION_TYPE_FIXED_TRANSFORM)); + final Rect outFrame = new Rect(); final Rect outInsets = new Rect(); final Rect outStableInsets = new Rect(); @@ -1132,6 +1144,7 @@ public class DisplayContentTests extends WindowTestsBase { assertFalse(app.hasFixedRotationTransform()); assertFalse(app2.hasFixedRotationTransform()); assertEquals(config90.orientation, mDisplayContent.getConfiguration().orientation); + assertNull(mDisplayContent.getFixedRotationAnimationController()); } @Test @@ -1310,7 +1323,7 @@ public class DisplayContentTests extends WindowTestsBase { } private static int getRotatedOrientation(DisplayContent dc) { - return dc.getLastOrientation() == SCREEN_ORIENTATION_LANDSCAPE + return dc.mBaseDisplayWidth > dc.mBaseDisplayHeight ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE; } diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index e47792f4920c..68bc58493f13 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -138,7 +138,7 @@ public class SizeCompatTests extends ActivityTestsBase { // Rotation is ignored so because the display size is close to square (700/600<1.333). assertTrue(mActivity.mDisplayContent.ignoreRotationForApps()); - final Rect displayBounds = mActivity.mDisplayContent.getBounds(); + final Rect displayBounds = mActivity.mDisplayContent.getWindowConfiguration().getBounds(); final float aspectRatio = 1.2f; mActivity.info.minAspectRatio = mActivity.info.maxAspectRatio = aspectRatio; prepareUnresizable(-1f, SCREEN_ORIENTATION_UNSPECIFIED); @@ -160,13 +160,22 @@ public class SizeCompatTests extends ActivityTestsBase { assertFitted(); // After the orientation of activity is changed, even display is not rotated, the aspect - // ratio should be the same (bounds=[0, 0 - 600, 600], appBounds=[0, 100 - 600, 600]). + // ratio should be the same (appBounds=[9, 100 - 592, 800], x-offset=round((600-583)/2)=9). assertEquals(appBounds.width(), appBounds.height() * aspectRatio, 0.5f /* delta */); // The notch is still on top. assertEquals(mActivity.getBounds().height(), appBounds.height() + notchHeight); mActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); assertFitted(); + + // Close-to-square display can rotate without being restricted by the requested orientation. + // The notch becomes on the left side. The activity is horizontal centered in 100 ~ 800. + // So the bounds and appBounds will be [200, 0 - 700, 600] (500x600) that is still fitted. + // Left = 100 + (800 - 100 - 500) / 2 = 200. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + assertFitted(); + assertEquals(appBounds.left, + notchHeight + (displayBounds.width() - notchHeight - appBounds.width()) / 2); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 8ce5daa635f2..e9ed20bd9683 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -603,6 +603,29 @@ public class WindowStateTests extends WindowTestsBase { } @Test + public void testRequestResizeForBlastSync() { + final WindowState win = mChildAppWindowAbove; + makeWindowVisible(win, win.getParentWindow()); + win.mLayoutSeq = win.getDisplayContent().mLayoutSeq; + win.reportResized(); + win.updateResizingWindowIfNeeded(); + assertThat(mWm.mResizingWindows).doesNotContain(win); + + // Check that the window is in resizing if using blast sync. + win.reportResized(); + win.prepareForSync(mock(BLASTSyncEngine.TransactionReadyListener.class), 1); + win.updateResizingWindowIfNeeded(); + assertThat(mWm.mResizingWindows).contains(win); + + // Don't re-add the window again if it's been reported to the client and still waiting on + // the client draw for blast sync. + win.reportResized(); + mWm.mResizingWindows.remove(win); + win.updateResizingWindowIfNeeded(); + assertThat(mWm.mResizingWindows).doesNotContain(win); + } + + @Test public void testGetTransformationMatrix() { final int PARENT_WINDOW_OFFSET = 1; final int DISPLAY_IN_PARENT_WINDOW_OFFSET = 2; |