diff options
74 files changed, 1048 insertions, 623 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 805dfafe5923..37ceb091f035 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -1382,6 +1382,12 @@ public class JobInfo implements Parcelable { * Calling this method will override any requirements previously defined * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only * want to call one of these methods. + * + * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * an app must hold the {@link android.Manifest.permission#INTERNET} and + * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to + * schedule a job that requires a network. + * * <p class="note"> * When your job executes in * {@link JobService#onStartJob(JobParameters)}, be sure to use the @@ -1438,6 +1444,11 @@ public class JobInfo implements Parcelable { * otherwise you'll use the default network which may not meet this * constraint. * + * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * an app must hold the {@link android.Manifest.permission#INTERNET} and + * {@link android.Manifest.permission#ACCESS_NETWORK_STATE} permissions to + * schedule a job that requires a network. + * * @param networkRequest The detailed description of the kind of network * this job requires, or {@code null} if no specific kind of * network is required. Defining a {@link NetworkSpecifier} diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java index 32502eddc9f8..bf4f9a83b99c 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java @@ -481,6 +481,10 @@ public class JobParameters implements Parcelable { * such as allowing a {@link JobInfo#NETWORK_TYPE_UNMETERED} job to run over * a metered network when there is a surplus of metered data available. * + * Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, + * this will return {@code null} if the app does not hold the permissions specified in + * {@link JobInfo.Builder#setRequiredNetwork(NetworkRequest)}. + * * @return the network that should be used to perform any network requests * for this job, or {@code null} if this job didn't set any required * network type or if the job executed when there was no available network to use. diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java index d94993d64995..d06596fa18aa 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static android.text.format.DateUtils.MINUTE_IN_MILLIS; +import android.Manifest; import android.annotation.EnforcePermission; import android.annotation.NonNull; import android.annotation.Nullable; @@ -190,6 +191,14 @@ public class JobSchedulerService extends com.android.server.SystemService @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) private static final long REQUIRE_NETWORK_CONSTRAINT_FOR_NETWORK_JOB_WORK_ITEMS = 241104082L; + /** + * Require the app to have the INTERNET and ACCESS_NETWORK_STATE permissions when scheduling + * a job with a connectivity constraint. + */ + @ChangeId + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + static final long REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS = 271850009L; + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public static Clock sSystemClock = Clock.systemUTC(); @@ -299,6 +308,14 @@ public class JobSchedulerService extends com.android.server.SystemService private final RemoteCallbackList<IUserVisibleJobObserver> mUserVisibleJobObservers = new RemoteCallbackList<>(); + /** + * Cache of grant status of permissions, keyed by UID->PID->permission name. A missing value + * means the state has not been queried. + */ + @GuardedBy("mPermissionCache") + private final SparseArray<SparseArrayMap<String, Boolean>> mPermissionCache = + new SparseArray<>(); + private final CountQuotaTracker mQuotaTracker; private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()"; private static final String QUOTA_TRACKER_SCHEDULE_LOGGED = @@ -1042,6 +1059,10 @@ public class JobSchedulerService extends com.android.server.SystemService final int pkgUid = intent.getIntExtra(Intent.EXTRA_UID, -1); if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) { + synchronized (mPermissionCache) { + // Something changed. Better clear the cached permission set. + mPermissionCache.remove(pkgUid); + } // Purge the app's jobs if the whole package was just disabled. When this is // the case the component name will be a bare package name. if (pkgName != null && pkgUid != -1) { @@ -1106,17 +1127,19 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.w(TAG, "PACKAGE_CHANGED for " + pkgName + " / uid " + pkgUid); } } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + synchronized (mPermissionCache) { + // Something changed. Better clear the cached permission set. + mPermissionCache.remove(pkgUid); + } if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { - final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); synchronized (mLock) { - mUidToPackageCache.remove(uid); - } - } else { - synchronized (mJobSchedulerStub.mPersistCache) { - mJobSchedulerStub.mPersistCache.remove(pkgUid); + mUidToPackageCache.remove(pkgUid); } } } else if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { + synchronized (mPermissionCache) { + mPermissionCache.remove(pkgUid); + } if (DEBUG) { Slog.d(TAG, "Removing jobs for " + pkgName + " (uid=" + pkgUid + ")"); } @@ -1155,6 +1178,14 @@ public class JobSchedulerService extends com.android.server.SystemService } } mConcurrencyManager.onUserRemoved(userId); + synchronized (mPermissionCache) { + for (int u = mPermissionCache.size() - 1; u >= 0; --u) { + final int uid = mPermissionCache.keyAt(u); + if (userId == UserHandle.getUserId(uid)) { + mPermissionCache.removeAt(u); + } + } + } } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) { // Has this package scheduled any jobs, such that we will take action // if it were to be force-stopped? @@ -3748,18 +3779,38 @@ public class JobSchedulerService extends com.android.server.SystemService } /** + * Returns whether the app has the permission granted. + * This currently only works for normal permissions and <b>DOES NOT</b> work for runtime + * permissions. + * TODO: handle runtime permissions + */ + private boolean hasPermission(int uid, int pid, @NonNull String permission) { + synchronized (mPermissionCache) { + SparseArrayMap<String, Boolean> pidPermissions = mPermissionCache.get(uid); + if (pidPermissions == null) { + pidPermissions = new SparseArrayMap<>(); + mPermissionCache.put(uid, pidPermissions); + } + final Boolean cached = pidPermissions.get(pid, permission); + if (cached != null) { + return cached; + } + + final int result = getContext().checkPermission(permission, pid, uid); + final boolean permissionGranted = (result == PackageManager.PERMISSION_GRANTED); + pidPermissions.add(pid, permission, permissionGranted); + return permissionGranted; + } + } + + /** * Binder stub trampoline implementation */ final class JobSchedulerStub extends IJobScheduler.Stub { - /** - * Cache determination of whether a given app can persist jobs - * key is uid of the calling app; value is undetermined/true/false - */ - private final SparseArray<Boolean> mPersistCache = new SparseArray<Boolean>(); - // Enforce that only the app itself (or shared uid participant) can schedule a // job that runs one of the app's services, as well as verifying that the // named service properly requires the BIND_JOB_SERVICE permission + // TODO(141645789): merge enforceValidJobRequest() with validateJob() private void enforceValidJobRequest(int uid, int pid, JobInfo job) { final PackageManager pm = getContext() .createContextAsUser(UserHandle.getUserHandleForUid(uid), 0) @@ -3784,31 +3835,33 @@ public class JobSchedulerService extends com.android.server.SystemService throw new IllegalArgumentException( "Tried to schedule job for non-existent component: " + service); } + // If we get this far we're good to go; all we need to do now is check + // whether the app is allowed to persist its scheduled work. if (job.isPersisted() && !canPersistJobs(pid, uid)) { throw new IllegalArgumentException("Requested job cannot be persisted without" + " holding android.permission.RECEIVE_BOOT_COMPLETED permission"); } + if (job.getRequiredNetwork() != null + && CompatChanges.isChangeEnabled( + REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) { + // All networking, including with the local network and even local to the device, + // requires the INTERNET permission. + if (!hasPermission(uid, pid, Manifest.permission.INTERNET)) { + throw new SecurityException(Manifest.permission.INTERNET + + " required for jobs with a connectivity constraint"); + } + if (!hasPermission(uid, pid, Manifest.permission.ACCESS_NETWORK_STATE)) { + throw new SecurityException(Manifest.permission.ACCESS_NETWORK_STATE + + " required for jobs with a connectivity constraint"); + } + } } private boolean canPersistJobs(int pid, int uid) { - // If we get this far we're good to go; all we need to do now is check - // whether the app is allowed to persist its scheduled work. - final boolean canPersist; - synchronized (mPersistCache) { - Boolean cached = mPersistCache.get(uid); - if (cached != null) { - canPersist = cached.booleanValue(); - } else { - // Persisting jobs is tantamount to running at boot, so we permit - // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED - // permission - int result = getContext().checkPermission( - android.Manifest.permission.RECEIVE_BOOT_COMPLETED, pid, uid); - canPersist = (result == PackageManager.PERMISSION_GRANTED); - mPersistCache.put(uid, canPersist); - } - } - return canPersist; + // Persisting jobs is tantamount to running at boot, so we permit + // it when the app has declared that it uses the RECEIVE_BOOT_COMPLETED + // permission + return hasPermission(uid, pid, Manifest.permission.RECEIVE_BOOT_COMPLETED); } private int validateJob(@NonNull JobInfo job, int callingUid, int callingPid, @@ -4027,6 +4080,8 @@ public class JobSchedulerService extends com.android.server.SystemService + " not permitted to schedule jobs for other apps"); } + enforceValidJobRequest(callerUid, callerPid, job); + int result = validateJob(job, callerUid, callerPid, userId, packageName, null); if (result != JobScheduler.RESULT_SUCCESS) { return result; diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java index b080bf31fed4..1e2ef7755664 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java @@ -21,6 +21,7 @@ import static android.app.job.JobInfo.getPriorityString; import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; +import android.Manifest; import android.annotation.BytesLong; import android.annotation.NonNull; import android.annotation.Nullable; @@ -39,6 +40,7 @@ import android.compat.annotation.EnabledAfter; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.PermissionChecker; import android.content.ServiceConnection; import android.net.Network; import android.net.Uri; @@ -339,12 +341,13 @@ public final class JobServiceContext implements ServiceConnection { job.changedAuthorities.toArray(triggeredAuthorities); } final JobInfo ji = job.getJob(); + final Network passedNetwork = canGetNetworkInformation(job) ? job.network : null; mParams = new JobParameters(mRunningCallback, job.getNamespace(), job.getJobId(), ji.getExtras(), ji.getTransientExtras(), ji.getClipData(), ji.getClipGrantFlags(), isDeadlineExpired, job.shouldTreatAsExpeditedJob(), job.shouldTreatAsUserInitiatedJob(), triggeredUris, triggeredAuthorities, - job.network); + passedNetwork); mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis(); mMinExecutionGuaranteeMillis = mService.getMinJobExecutionGuaranteeMs(job); mMaxExecutionTimeMillis = @@ -504,6 +507,37 @@ public final class JobServiceContext implements ServiceConnection { } } + private boolean canGetNetworkInformation(@NonNull JobStatus job) { + if (job.getJob().getRequiredNetwork() == null) { + // The job never had a network constraint, so we're not going to give it a network + // object. Add this check as an early return to avoid wasting cycles doing permission + // checks for this job. + return false; + } + // The calling app is doing the work, so use its UID, not the source UID. + final int uid = job.getUid(); + if (CompatChanges.isChangeEnabled( + JobSchedulerService.REQUIRE_NETWORK_PERMISSIONS_FOR_CONNECTIVITY_JOBS, uid)) { + final String pkgName = job.getServiceComponent().getPackageName(); + if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.INTERNET)) { + return false; + } + if (!hasPermissionForDelivery(uid, pkgName, Manifest.permission.ACCESS_NETWORK_STATE)) { + return false; + } + } + + return true; + } + + private boolean hasPermissionForDelivery(int uid, @NonNull String pkgName, + @NonNull String permission) { + final int result = PermissionChecker.checkPermissionForDataDelivery(mContext, permission, + PermissionChecker.PID_UNKNOWN, uid, pkgName, /* attributionTag */ null, + "network info via JS"); + return result == PermissionChecker.PERMISSION_GRANTED; + } + @EconomicPolicy.AppAction private static int getStartActionId(@NonNull JobStatus job) { switch (job.getEffectivePriority()) { @@ -603,6 +637,15 @@ public final class JobServiceContext implements ServiceConnection { } void informOfNetworkChangeLocked(Network newNetwork) { + if (newNetwork != null && mRunningJob != null && !canGetNetworkInformation(mRunningJob)) { + // The app can't get network information, so there's no point informing it of network + // changes. This case may happen if an app had scheduled network job and then + // started targeting U+ without requesting the required network permissions. + if (DEBUG) { + Slog.d(TAG, "Skipping network change call because of missing permissions"); + } + return; + } if (mVerb != VERB_EXECUTING) { Slog.w(TAG, "Sending onNetworkChanged for a job that isn't started. " + mRunningJob); if (mVerb == VERB_BINDING || mVerb == VERB_STARTING) { diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index 58f78aa4fc15..c1f6219025f9 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -18,6 +18,7 @@ package android.app { public class ActivityManager { method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void addHomeVisibilityListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.HomeVisibilityListener); + method @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int[] getUidFrozenState(@NonNull int[]); method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void registerUidFrozenStateChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.ActivityManager.UidFrozenStateChangedCallback); method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener); method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void unregisterUidFrozenStateChangedCallback(@NonNull android.app.ActivityManager.UidFrozenStateChangedCallback); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b21028464426..fbc69e34a644 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -33,7 +33,6 @@ package android { field public static final String ADD_TRUSTED_DISPLAY = "android.permission.ADD_TRUSTED_DISPLAY"; field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"; field public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE"; - field public static final String ALLOWLISTED_WRITE_DEVICE_CONFIG = "android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG"; field public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission.ALLOW_ANY_CODEC_FOR_PLAYBACK"; field public static final String ALLOW_PLACE_IN_MULTI_PANE_SETTINGS = "android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS"; field public static final String ALLOW_SLIPPERY_TOUCHES = "android.permission.ALLOW_SLIPPERY_TOUCHES"; @@ -380,6 +379,7 @@ package android { field public static final String WIFI_SET_DEVICE_MOBILITY_STATE = "android.permission.WIFI_SET_DEVICE_MOBILITY_STATE"; field public static final String WIFI_UPDATE_COEX_UNSAFE_CHANNELS = "android.permission.WIFI_UPDATE_COEX_UNSAFE_CHANNELS"; field public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission.WIFI_UPDATE_USABILITY_STATS_SCORE"; + field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"; field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; field public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE"; field public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS"; diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 1a8ebb15663b..ed36f1c3d99d 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -5,7 +5,6 @@ package android { field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS"; field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING"; field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY"; - field public static final String ALLOWLISTED_WRITE_DEVICE_CONFIG = "android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG"; field public static final String APPROVE_INCIDENT_REPORTS = "android.permission.APPROVE_INCIDENT_REPORTS"; field public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA"; field public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission.BIND_CELL_BROADCAST_SERVICE"; @@ -55,6 +54,7 @@ package android { field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD"; field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS"; field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS"; + field public static final String WRITE_ALLOWLISTED_DEVICE_CONFIG = "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG"; field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG"; field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE"; field public static final String WRITE_OBB = "android.permission.WRITE_OBB"; @@ -131,11 +131,13 @@ package android.app { method public void alwaysShowUnsupportedCompileSdkWarning(android.content.ComponentName); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public int[] getDisplayIdsForStartingVisibleBackgroundUsers(); method public long getTotalRam(); + method @NonNull @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public int[] getUidFrozenState(@NonNull int[]); method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessCapabilities(int); method @RequiresPermission(allOf={android.Manifest.permission.PACKAGE_USAGE_STATS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}, conditional=true) public int getUidProcessState(int); method public void holdLock(android.os.IBinder, int); method public static boolean isHighEndGfx(); method public void notifySystemPropertiesChanged(); + method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void registerUidFrozenStateChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.app.ActivityManager.UidFrozenStateChangedCallback); method @RequiresPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) public void removeHomeVisibilityListener(@NonNull android.app.HomeVisibilityListener); method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors(); method public static void resumeAppSwitches() throws android.os.RemoteException; @@ -143,6 +145,7 @@ package android.app { method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int); method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public boolean startUserInBackgroundVisibleOnDisplay(int, int); method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean); + method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void unregisterUidFrozenStateChangedCallback(@NonNull android.app.ActivityManager.UidFrozenStateChangedCallback); method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String); method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle(); field public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L; // 0xa692aadL @@ -166,6 +169,12 @@ package android.app { method @Nullable public String getIconResourcePackage(); } + public static interface ActivityManager.UidFrozenStateChangedCallback { + method public void onUidFrozenStateChanged(@NonNull int[], @NonNull int[]); + field public static final int UID_FROZEN_STATE_FROZEN = 1; // 0x1 + field public static final int UID_FROZEN_STATE_UNFROZEN = 2; // 0x2 + } + public class ActivityOptions extends android.app.ComponentOptions { method public boolean isEligibleForLegacyPermissionPrompt(); method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener); @@ -2607,7 +2616,6 @@ package android.provider { field @Deprecated public static final String NOTIFICATION_BUBBLES = "notification_bubbles"; field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices"; field public static final String SHOW_FIRST_CRASH_DIALOG = "show_first_crash_dialog"; - field public static final String STYLUS_HANDWRITING_ENABLED = "stylus_handwriting_enabled"; field public static final String USER_DISABLED_HDR_FORMATS = "user_disabled_hdr_formats"; field public static final String USER_PREFERRED_REFRESH_RATE = "user_preferred_refresh_rate"; field public static final String USER_PREFERRED_RESOLUTION_HEIGHT = "user_preferred_resolution_height"; @@ -2639,6 +2647,8 @@ package android.provider { field public static final String SHOW_FIRST_CRASH_DIALOG_DEV_OPTION = "show_first_crash_dialog_dev_option"; field public static final String SHOW_IME_WITH_HARD_KEYBOARD = "show_ime_with_hard_keyboard"; field public static final String STYLUS_BUTTONS_ENABLED = "stylus_buttons_enabled"; + field public static final int STYLUS_HANDWRITING_DEFAULT_VALUE = 1; // 0x1 + field public static final String STYLUS_HANDWRITING_ENABLED = "stylus_handwriting_enabled"; field @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final String SYNC_PARENT_SOUNDS = "sync_parent_sounds"; field public static final String VOICE_INTERACTION_SERVICE = "voice_interaction_service"; } diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 929c07bc1dc5..21097079540d 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -256,6 +256,7 @@ public class ActivityManager { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @TestApi public interface UidFrozenStateChangedCallback { /** * Indicates that the UID was frozen. @@ -263,6 +264,7 @@ public class ActivityManager { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @TestApi int UID_FROZEN_STATE_FROZEN = 1; /** @@ -271,6 +273,7 @@ public class ActivityManager { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @TestApi int UID_FROZEN_STATE_UNFROZEN = 2; /** @@ -296,6 +299,7 @@ public class ActivityManager { * @hide */ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @TestApi void onUidFrozenStateChanged(@NonNull int[] uids, @NonNull @UidFrozenState int[] frozenStates); } @@ -315,6 +319,7 @@ public class ActivityManager { */ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @TestApi public void registerUidFrozenStateChangedCallback( @NonNull Executor executor, @NonNull UidFrozenStateChangedCallback callback) { @@ -346,6 +351,7 @@ public class ActivityManager { */ @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @TestApi public void unregisterUidFrozenStateChangedCallback( @NonNull UidFrozenStateChangedCallback callback) { Preconditions.checkNotNull(callback, "callback cannot be null"); @@ -363,6 +369,30 @@ public class ActivityManager { } /** + * Query the frozen state of a list of UIDs. + * + * @param uids the array of UIDs which the client would like to know the frozen state of. + * @return An array containing the frozen state for each requested UID, by index. Will be set + * to {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_FROZEN} + * if the UID is frozen. If the UID is not frozen or not found, + * {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_UNFROZEN} + * will be set. + * + * @hide + */ + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @TestApi + public @NonNull @UidFrozenStateChangedCallback.UidFrozenState + int[] getUidFrozenState(@NonNull int[] uids) { + try { + return getService().getUidFrozenState(uids); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">{@code * <meta-data>}</a> name for a 'home' Activity that declares a package that is to be * uninstalled in lieu of the declaring one. The package named here must be diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 97d45623d3da..91eb4c44cda5 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -882,4 +882,6 @@ interface IActivityManager { void registerUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") void unregisterUidFrozenStateChangedCallback(in IUidFrozenStateChangedCallback callback); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS)") + int[] getUidFrozenState(in int[] uids); } diff --git a/core/java/android/app/IUserSwitchObserver.aidl b/core/java/android/app/IUserSwitchObserver.aidl index 234da8f36e96..cfdb426d6026 100644 --- a/core/java/android/app/IUserSwitchObserver.aidl +++ b/core/java/android/app/IUserSwitchObserver.aidl @@ -20,6 +20,7 @@ import android.os.IRemoteCallback; /** {@hide} */ oneway interface IUserSwitchObserver { + void onBeforeUserSwitching(int newUserId); void onUserSwitching(int newUserId, IRemoteCallback reply); void onUserSwitchComplete(int newUserId); void onForegroundProfileSwitch(int newProfileId); diff --git a/core/java/android/app/UserSwitchObserver.java b/core/java/android/app/UserSwitchObserver.java index 6abc4f09ba38..727799a1f948 100644 --- a/core/java/android/app/UserSwitchObserver.java +++ b/core/java/android/app/UserSwitchObserver.java @@ -30,6 +30,9 @@ public class UserSwitchObserver extends IUserSwitchObserver.Stub { } @Override + public void onBeforeUserSwitching(int newUserId) throws RemoteException {} + + @Override public void onUserSwitching(int newUserId, IRemoteCallback reply) throws RemoteException { if (reply != null) { reply.sendResult(null); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 924a7c659b08..bad6c77a17f3 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -15775,7 +15775,7 @@ public class DevicePolicyManager { throwIfParentInstance("setApplicationExemptions"); if (mService != null) { try { - mService.setApplicationExemptions(packageName, + mService.setApplicationExemptions(mContext.getPackageName(), packageName, ArrayUtils.convertToIntArray(new ArraySet<>(exemptions))); } catch (ServiceSpecificException e) { switch (e.errorCode) { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index e202ac2c9245..8d508c0fb79d 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -591,7 +591,7 @@ interface IDevicePolicyManager { List<UserHandle> getPolicyManagedProfiles(in UserHandle userHandle); - void setApplicationExemptions(String packageName, in int[]exemptions); + void setApplicationExemptions(String callerPackage, String packageName, in int[]exemptions); int[] getApplicationExemptions(String packageName); void setMtePolicy(int flag, String callerPackageName); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 7ae280fd7d90..c473d3f81823 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -7113,6 +7113,28 @@ public final class Settings { "input_method_selector_visibility"; /** + * Toggle for enabling stylus handwriting. When enabled, current Input method receives + * stylus {@link MotionEvent}s if an {@link Editor} is focused. + * + * @see #STYLUS_HANDWRITING_DEFAULT_VALUE + * @hide + */ + @TestApi + @Readable + @SuppressLint("NoSettingsProvider") + public static final String STYLUS_HANDWRITING_ENABLED = "stylus_handwriting_enabled"; + + /** + * Default value for {@link #STYLUS_HANDWRITING_ENABLED}. + * + * @hide + */ + @TestApi + @Readable + @SuppressLint("NoSettingsProvider") + public static final int STYLUS_HANDWRITING_DEFAULT_VALUE = 1; + + /** * The currently selected voice interaction service flattened ComponentName. * @hide */ @@ -16482,17 +16504,6 @@ public final class Settings { public static final String AUTOFILL_MAX_VISIBLE_DATASETS = "autofill_max_visible_datasets"; /** - * Toggle for enabling stylus handwriting. When enabled, current Input method receives - * stylus {@link MotionEvent}s if an {@link Editor} is focused. - * - * @hide - */ - @TestApi - @Readable - @SuppressLint("NoSettingsProvider") - public static final String STYLUS_HANDWRITING_ENABLED = "stylus_handwriting_enabled"; - - /** * Indicates whether a stylus has ever been used on the device. * * @hide diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index d84acc03826b..ce2c18080b91 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -508,6 +508,7 @@ final class IInputMethodManagerGlobalInvoker { @AnyThread static void prepareStylusHandwritingDelegation( @NonNull IInputMethodClient client, + @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName) { final IInputMethodManager service = getService(); @@ -516,7 +517,7 @@ final class IInputMethodManagerGlobalInvoker { } try { service.prepareStylusHandwritingDelegation( - client, delegatePackageName, delegatorPackageName); + client, userId, delegatePackageName, delegatorPackageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -525,6 +526,7 @@ final class IInputMethodManagerGlobalInvoker { @AnyThread static boolean acceptStylusHandwritingDelegation( @NonNull IInputMethodClient client, + @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName) { final IInputMethodManager service = getService(); @@ -533,7 +535,7 @@ final class IInputMethodManagerGlobalInvoker { } try { return service.acceptStylusHandwritingDelegation( - client, delegatePackageName, delegatorPackageName); + client, userId, delegatePackageName, delegatorPackageName); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 36d2b8a89779..515b95cd951d 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -1553,9 +1553,7 @@ public final class InputMethodManager { if (fallbackContext == null) { return false; } - if (!isStylusHandwritingEnabled(fallbackContext)) { - return false; - } + return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(userId); } @@ -2244,11 +2242,6 @@ public final class InputMethodManager { } boolean useDelegation = !TextUtils.isEmpty(delegatorPackageName); - if (!isStylusHandwritingEnabled(view.getContext())) { - Log.w(TAG, "Stylus handwriting pref is disabled. " - + "Ignoring calls to start stylus handwriting."); - return false; - } checkFocus(); synchronized (mH) { @@ -2264,7 +2257,8 @@ public final class InputMethodManager { } if (useDelegation) { return IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegation( - mClient, view.getContext().getOpPackageName(), delegatorPackageName); + mClient, UserHandle.myUserId(), view.getContext().getOpPackageName(), + delegatorPackageName); } else { IInputMethodManagerGlobalInvoker.startStylusHandwriting(mClient); } @@ -2272,15 +2266,6 @@ public final class InputMethodManager { } } - private boolean isStylusHandwritingEnabled(@NonNull Context context) { - if (Settings.Global.getInt(context.getContentResolver(), - Settings.Global.STYLUS_HANDWRITING_ENABLED, 0) == 0) { - Log.d(TAG, "Stylus handwriting pref is disabled."); - return false; - } - return true; - } - /** * Prepares delegation of starting stylus handwriting session to a different editor in same * or different window than the view on which initial handwriting stroke was detected. @@ -2344,13 +2329,9 @@ public final class InputMethodManager { fallbackImm.prepareStylusHandwritingDelegation(delegatorView, delegatePackageName); } - if (!isStylusHandwritingEnabled(delegatorView.getContext())) { - Log.w(TAG, "Stylus handwriting pref is disabled. " - + "Ignoring prepareStylusHandwritingDelegation()."); - return; - } IInputMethodManagerGlobalInvoker.prepareStylusHandwritingDelegation( mClient, + UserHandle.myUserId(), delegatePackageName, delegatorView.getContext().getOpPackageName()); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 593f3dbb99de..d56a06fbd127 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -11497,7 +11497,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener changed = true; } - if (requestRectWithoutFocus && isFocused()) { + if (requestRectWithoutFocus || isFocused()) { // This offsets because getInterestingRect() is in terms of viewport coordinates, but // requestRectangleOnScreen() is in terms of content coordinates. diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 9a4610e8c0a1..549169388e45 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -150,12 +150,13 @@ interface IInputMethodManager { /** Prepares delegation of starting stylus handwriting session to a different editor **/ void prepareStylusHandwritingDelegation(in IInputMethodClient client, + in int userId, in String delegatePackageName, in String delegatorPackageName); /** Accepts and starts a stylus handwriting session for the delegate view **/ boolean acceptStylusHandwritingDelegation(in IInputMethodClient client, - in String delegatePackageName, in String delegatorPackageName); + in int userId, in String delegatePackageName, in String delegatorPackageName); /** Returns {@code true} if currently selected IME supports Stylus handwriting. */ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = " diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 393e7785b446..81b1f21e286d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -4205,14 +4205,14 @@ <permission android:name="android.permission.WRITE_DEVICE_CONFIG" android:protectionLevel="signature|verifier|configurator"/> - <!-- @SystemApi @TestApi @hide Allows an application to read/write sync disabled mode config. + <!-- @SystemApi @TestApi @hide Allows an application to modify only allowlisted settings. <p>Not for use by third-party applications. --> - <permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" + <permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" android:protectionLevel="signature|verifier|configurator"/> - <!-- @SystemApi @TestApi @hide Allows an application to modify only allowlisted settings. + <!-- @SystemApi @TestApi @hide Allows an application to read/write sync disabled mode config. <p>Not for use by third-party applications. --> - <permission android:name="android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG" + <permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" android:protectionLevel="signature|verifier|configurator"/> <!-- @SystemApi @hide Allows an application to read config settings. diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java index 22462eb11a8f..51f99ec637da 100644 --- a/graphics/java/android/graphics/ImageDecoder.java +++ b/graphics/java/android/graphics/ImageDecoder.java @@ -38,11 +38,8 @@ import android.graphics.drawable.AnimatedImageDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.NinePatchDrawable; -import android.media.MediaCodecInfo; -import android.media.MediaCodecList; import android.net.Uri; import android.os.Build; -import android.os.SystemProperties; import android.os.Trace; import android.system.ErrnoException; import android.system.Os; @@ -931,8 +928,6 @@ public final class ImageDecoder implements AutoCloseable { case "image/x-pentax-pef": case "image/x-samsung-srw": return true; - case "image/avif": - return isP010SupportedForAV1(); default: return false; } @@ -2068,49 +2063,6 @@ public final class ImageDecoder implements AutoCloseable { return decodeBitmapImpl(src, null); } - private static boolean sIsP010SupportedForAV1 = false; - private static boolean sIsP010SupportedForAV1Initialized = false; - - /** - * Checks if the device supports decoding 10-bit AV1. - */ - private static boolean isP010SupportedForAV1() { - if (sIsP010SupportedForAV1Initialized) { - return sIsP010SupportedForAV1; - } - - sIsP010SupportedForAV1Initialized = true; - - if (hasHardwareDecoder("video/av01")) { - sIsP010SupportedForAV1 = true; - return true; - } - - sIsP010SupportedForAV1 = SystemProperties.getInt("ro.product.first_api_level", 0) >= - Build.VERSION_CODES.S; - return sIsP010SupportedForAV1; - } - - /** - * Checks if the device has hardware decoder for the target mime type. - */ - private static boolean hasHardwareDecoder(String mime) { - final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS); - for (MediaCodecInfo info : sMCL.getCodecInfos()) { - if (info.isEncoder() == false && info.isHardwareAccelerated()) { - try { - if (info.getCapabilitiesForType(mime) != null) { - return true; - } - } catch (IllegalArgumentException e) { - // mime is not supported - return false; - } - } - } - return false; - } - /** * Private method called by JNI. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 4805ed39e1a2..5f2b63089009 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -656,6 +656,13 @@ public class Bubble implements BubbleViewProvider { } /** + * Whether this bubble is conversation + */ + public boolean isConversation() { + return null != mShortcutInfo; + } + + /** * Sets whether this notification should be suppressed in the shade. */ @VisibleForTesting diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java index deb4fd5f19bb..66241628fc77 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java @@ -33,7 +33,6 @@ import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.SuppressLint; -import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -575,7 +574,7 @@ public class BubbleStackView extends FrameLayout if (maybeShowStackEdu()) { mShowedUserEducationInTouchListenerActive = true; return true; - } else if (isStackEduShowing()) { + } else if (isStackEduVisible()) { mStackEduView.hide(false /* fromExpansion */); } @@ -651,7 +650,7 @@ public class BubbleStackView extends FrameLayout mExpandedAnimationController.dragBubbleOut( v, viewInitialX + dx, viewInitialY + dy); } else { - if (isStackEduShowing()) { + if (isStackEduVisible()) { mStackEduView.hide(false /* fromExpansion */); } mStackAnimationController.moveStackFromTouch( @@ -733,8 +732,7 @@ public class BubbleStackView extends FrameLayout @Override public void onMove(float dx, float dy) { - if ((mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) - || isStackEduShowing()) { + if (isManageEduVisible() || isStackEduVisible()) { return; } @@ -996,7 +994,7 @@ public class BubbleStackView extends FrameLayout mStackAnimationController.updateResources(); mBubbleOverflow.updateResources(); - if (!isStackEduShowing() && mRelativeStackPositionBeforeRotation != null) { + if (!isStackEduVisible() && mRelativeStackPositionBeforeRotation != null) { mStackAnimationController.setStackPosition( mRelativeStackPositionBeforeRotation); mRelativeStackPositionBeforeRotation = null; @@ -1046,9 +1044,9 @@ public class BubbleStackView extends FrameLayout setOnClickListener(view -> { if (mShowingManage) { showManageMenu(false /* show */); - } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { + } else if (isManageEduVisible()) { mManageEduView.hide(); - } else if (isStackEduShowing()) { + } else if (isStackEduVisible()) { mStackEduView.hide(false /* isExpanding */); } else if (mBubbleData.isExpanded()) { mBubbleData.setExpanded(false); @@ -1247,10 +1245,19 @@ public class BubbleStackView extends FrameLayout } /** + * Whether the selected bubble is conversation bubble + */ + private boolean isConversationBubble() { + BubbleViewProvider bubble = mBubbleData.getSelectedBubble(); + return bubble instanceof Bubble && ((Bubble) bubble).isConversation(); + } + + /** * Whether the educational view should show for the expanded view "manage" menu. */ private boolean shouldShowManageEdu() { - if (ActivityManager.isRunningInTestHarness()) { + if (!isConversationBubble()) { + // We only show user education for conversation bubbles right now return false; } final boolean seen = getPrefBoolean(ManageEducationViewKt.PREF_MANAGED_EDUCATION); @@ -1273,11 +1280,17 @@ public class BubbleStackView extends FrameLayout mManageEduView.show(mExpandedBubble.getExpandedView()); } + @VisibleForTesting + public boolean isManageEduVisible() { + return mManageEduView != null && mManageEduView.getVisibility() == VISIBLE; + } + /** * Whether education view should show for the collapsed stack. */ private boolean shouldShowStackEdu() { - if (ActivityManager.isRunningInTestHarness()) { + if (!isConversationBubble()) { + // We only show user education for conversation bubbles right now return false; } final boolean seen = getPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION); @@ -1310,13 +1323,14 @@ public class BubbleStackView extends FrameLayout return mStackEduView.show(mPositioner.getDefaultStartPosition()); } - private boolean isStackEduShowing() { + @VisibleForTesting + public boolean isStackEduVisible() { return mStackEduView != null && mStackEduView.getVisibility() == VISIBLE; } // Recreates & shows the education views. Call when a theme/config change happens. private void updateUserEdu() { - if (isStackEduShowing()) { + if (isStackEduVisible()) { removeView(mStackEduView); mStackEduView = new StackEducationView(mContext, mPositioner, mBubbleController); addView(mStackEduView); @@ -1325,7 +1339,7 @@ public class BubbleStackView extends FrameLayout mStackAnimationController.setStackPosition(mPositioner.getDefaultStartPosition()); mStackEduView.show(mPositioner.getDefaultStartPosition()); } - if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { + if (isManageEduVisible()) { removeView(mManageEduView); mManageEduView = new ManageEducationView(mContext, mPositioner); addView(mManageEduView); @@ -1429,7 +1443,7 @@ public class BubbleStackView extends FrameLayout mStackAnimationController.updateResources(); mDismissView.updateResources(); mMagneticTarget.setMagneticFieldRadiusPx(mBubbleSize * 2); - if (!isStackEduShowing()) { + if (!isStackEduVisible()) { mStackAnimationController.setStackPosition( new RelativeStackPosition( mPositioner.getRestingPosition(), @@ -2013,7 +2027,7 @@ public class BubbleStackView extends FrameLayout if (mIsExpanded) { if (mShowingManage) { showManageMenu(false); - } else if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { + } else if (isManageEduVisible()) { mManageEduView.hide(); } else { mBubbleData.setExpanded(false); @@ -2158,7 +2172,7 @@ public class BubbleStackView extends FrameLayout cancelDelayedExpandCollapseSwitchAnimations(); final boolean showVertically = mPositioner.showBubblesVertically(); mIsExpanded = true; - if (isStackEduShowing()) { + if (isStackEduVisible()) { mStackEduView.hide(true /* fromExpansion */); } beforeExpandedViewAnimation(); @@ -2280,7 +2294,7 @@ public class BubbleStackView extends FrameLayout private void animateCollapse() { cancelDelayedExpandCollapseSwitchAnimations(); - if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) { + if (isManageEduVisible()) { mManageEduView.hide(); } @@ -2677,7 +2691,7 @@ public class BubbleStackView extends FrameLayout if (flyoutMessage == null || flyoutMessage.message == null || !bubble.showFlyout() - || isStackEduShowing() + || isStackEduVisible() || isExpanded() || mIsExpansionAnimating || mIsGestureInProgress @@ -2800,7 +2814,7 @@ public class BubbleStackView extends FrameLayout * them. */ public void getTouchableRegion(Rect outRect) { - if (isStackEduShowing()) { + if (isStackEduVisible()) { // When user education shows then capture all touches outRect.set(0, 0, getWidth(), getHeight()); return; diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java index e8f3f69ca64e..de967bfa288b 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java @@ -29,6 +29,8 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.content.Intent; +import android.content.pm.ShortcutInfo; +import android.content.res.Resources; import android.graphics.drawable.Icon; import android.os.Bundle; import android.service.notification.StatusBarNotification; @@ -162,4 +164,27 @@ public class BubbleTest extends ShellTestCase { verify(mBubbleMetadataFlagListener, never()).onBubbleMetadataFlagChanged(any()); } + + @Test + public void testBubbleIsConversation_hasConversationShortcut() { + Bubble bubble = createBubbleWithShortcut(); + assertThat(bubble.getShortcutInfo()).isNotNull(); + assertThat(bubble.isConversation()).isTrue(); + } + + @Test + public void testBubbleIsConversation_hasNoShortcut() { + Bubble bubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor); + assertThat(bubble.getShortcutInfo()).isNull(); + assertThat(bubble.isConversation()).isFalse(); + } + + private Bubble createBubbleWithShortcut() { + ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext) + .setId("mockShortcutId") + .build(); + return new Bubble("mockKey", shortcutInfo, 10, Resources.ID_NULL, + "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */, + mMainExecutor, mBubbleMetadataFlagListener); + } } diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java index 3ba1d1f0eca2..c1ee74a70a15 100644 --- a/media/java/android/media/audiopolicy/AudioPolicy.java +++ b/media/java/android/media/audiopolicy/AudioPolicy.java @@ -75,11 +75,13 @@ public class AudioPolicy { */ public static final int POLICY_STATUS_REGISTERED = 2; + @GuardedBy("mLock") private int mStatus; + @GuardedBy("mLock") private String mRegistrationId; - private AudioPolicyStatusListener mStatusListener; - private boolean mIsFocusPolicy; - private boolean mIsTestFocusPolicy; + private final AudioPolicyStatusListener mStatusListener; + private final boolean mIsFocusPolicy; + private final boolean mIsTestFocusPolicy; /** * The list of AudioTrack instances created to inject audio into the associated mixes @@ -115,6 +117,7 @@ public class AudioPolicy { private Context mContext; + @GuardedBy("mLock") private AudioPolicyConfig mConfig; private final MediaProjection mProjection; @@ -552,7 +555,6 @@ public class AudioPolicy { /** @hide */ public void reset() { setRegistration(null); - mConfig.reset(); } public void setRegistration(String regId) { @@ -563,6 +565,7 @@ public class AudioPolicy { mStatus = POLICY_STATUS_REGISTERED; } else { mStatus = POLICY_STATUS_UNREGISTERED; + mConfig.reset(); } } sendMsg(MSG_POLICY_STATUS_CHANGE); @@ -940,14 +943,9 @@ public class AudioPolicy { } private void onPolicyStatusChange() { - AudioPolicyStatusListener l; - synchronized (mLock) { - if (mStatusListener == null) { - return; - } - l = mStatusListener; + if (mStatusListener != null) { + mStatusListener.onStatusChange(); } - l.onStatusChange(); } //================================================== diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java index ce9773312a10..7a85d21bf144 100644 --- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java +++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java @@ -42,9 +42,7 @@ public class AudioPolicyConfig implements Parcelable { private String mRegistrationId = null; - /** counter for the mixes that are / have been in the list of AudioMix - * e.g. register 4 mixes (counter is 3), remove 1 (counter is 3), add 1 (counter is 4) - */ + // Corresponds to id of next mix to be registered. private int mMixCounter = 0; protected AudioPolicyConfig(AudioPolicyConfig conf) { @@ -286,7 +284,7 @@ public class AudioPolicyConfig implements Parcelable { if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) == AudioMix.ROUTE_FLAG_LOOP_BACK) { mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":" - + mMixCounter); + + mMixCounter++); } else if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_RENDER) == AudioMix.ROUTE_FLAG_RENDER) { mix.setRegistration(mix.mDeviceAddress); @@ -294,7 +292,6 @@ public class AudioPolicyConfig implements Parcelable { } else { mix.setRegistration(""); } - mMixCounter++; } @GuardedBy("mMixes") diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java index 250187f210dc..df0e61833269 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java @@ -312,12 +312,6 @@ public class DataServiceUtils { "isFirstRemovableSubscription"; /** - * The name of the default SIM config column, - * {@see SubscriptionUtil#getDefaultSimConfig(Context, int)}. - */ - public static final String COLUMN_DEFAULT_SIM_CONFIG = "defaultSimConfig"; - - /** * The name of the default subscription selection column, * {@see SubscriptionUtil#getSubscriptionOrDefault(Context, int)}. */ @@ -349,32 +343,6 @@ public class DataServiceUtils { public static final String COLUMN_IS_AVAILABLE_SUBSCRIPTION = "isAvailableSubscription"; /** - * The name of the default voice subscription state column, see - * {@link SubscriptionManager#getDefaultVoiceSubscriptionId()}. - */ - public static final String COLUMN_IS_DEFAULT_VOICE_SUBSCRIPTION = - "isDefaultVoiceSubscription"; - - /** - * The name of the default sms subscription state column, see - * {@link SubscriptionManager#getDefaultSmsSubscriptionId()}. - */ - public static final String COLUMN_IS_DEFAULT_SMS_SUBSCRIPTION = "isDefaultSmsSubscription"; - - /** - * The name of the default data subscription state column, see - * {@link SubscriptionManager#getDefaultDataSubscriptionId()}. - */ - public static final String COLUMN_IS_DEFAULT_DATA_SUBSCRIPTION = - "isDefaultDataSubscription"; - - /** - * The name of the default subscription state column, see - * {@link SubscriptionManager#getDefaultSubscriptionId()}. - */ - public static final String COLUMN_IS_DEFAULT_SUBSCRIPTION = "isDefaultSubscription"; - - /** * The name of the active data subscription state column, see * {@link SubscriptionManager#getActiveDataSubscriptionId()}. */ diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java index 23566f760444..c40388fee710 100644 --- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java +++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java @@ -37,12 +37,10 @@ public class SubscriptionInfoEntity { String countryIso, boolean isEmbedded, int cardId, int portIndex, boolean isOpportunistic, @Nullable String groupUUID, int subscriptionType, String uniqueName, boolean isSubscriptionVisible, String formattedPhoneNumber, - boolean isFirstRemovableSubscription, String defaultSimConfig, - boolean isDefaultSubscriptionSelection, boolean isValidSubscription, - boolean isUsableSubscription, boolean isActiveSubscriptionId, - boolean isAvailableSubscription, boolean isDefaultVoiceSubscription, - boolean isDefaultSmsSubscription, boolean isDefaultDataSubscription, - boolean isDefaultSubscription, boolean isActiveDataSubscriptionId) { + boolean isFirstRemovableSubscription, boolean isDefaultSubscriptionSelection, + boolean isValidSubscription, boolean isUsableSubscription, + boolean isActiveSubscriptionId, boolean isAvailableSubscription, + boolean isActiveDataSubscriptionId) { this.subId = subId; this.simSlotIndex = simSlotIndex; this.carrierId = carrierId; @@ -62,16 +60,11 @@ public class SubscriptionInfoEntity { this.isSubscriptionVisible = isSubscriptionVisible; this.formattedPhoneNumber = formattedPhoneNumber; this.isFirstRemovableSubscription = isFirstRemovableSubscription; - this.defaultSimConfig = defaultSimConfig; this.isDefaultSubscriptionSelection = isDefaultSubscriptionSelection; this.isValidSubscription = isValidSubscription; this.isUsableSubscription = isUsableSubscription; this.isActiveSubscriptionId = isActiveSubscriptionId; this.isAvailableSubscription = isAvailableSubscription; - this.isDefaultVoiceSubscription = isDefaultVoiceSubscription; - this.isDefaultSmsSubscription = isDefaultSmsSubscription; - this.isDefaultDataSubscription = isDefaultDataSubscription; - this.isDefaultSubscription = isDefaultSubscription; this.isActiveDataSubscriptionId = isActiveDataSubscriptionId; } @@ -135,9 +128,6 @@ public class SubscriptionInfoEntity { @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_FIRST_REMOVABLE_SUBSCRIPTION) public boolean isFirstRemovableSubscription; - @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_DEFAULT_SIM_CONFIG) - public String defaultSimConfig; - @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SUBSCRIPTION_SELECTION) public boolean isDefaultSubscriptionSelection; @@ -154,18 +144,6 @@ public class SubscriptionInfoEntity { @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_AVAILABLE_SUBSCRIPTION) public boolean isAvailableSubscription; - @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_VOICE_SUBSCRIPTION) - public boolean isDefaultVoiceSubscription; - - @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SMS_SUBSCRIPTION) - public boolean isDefaultSmsSubscription; - - @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_DATA_SUBSCRIPTION) - public boolean isDefaultDataSubscription; - - @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SUBSCRIPTION) - public boolean isDefaultSubscription; - @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION) public boolean isActiveDataSubscriptionId; @@ -207,16 +185,11 @@ public class SubscriptionInfoEntity { result = 31 * result + Boolean.hashCode(isSubscriptionVisible); result = 31 * result + formattedPhoneNumber.hashCode(); result = 31 * result + Boolean.hashCode(isFirstRemovableSubscription); - result = 31 * result + defaultSimConfig.hashCode(); result = 31 * result + Boolean.hashCode(isDefaultSubscriptionSelection); result = 31 * result + Boolean.hashCode(isValidSubscription); result = 31 * result + Boolean.hashCode(isUsableSubscription); result = 31 * result + Boolean.hashCode(isActiveSubscriptionId); result = 31 * result + Boolean.hashCode(isAvailableSubscription); - result = 31 * result + Boolean.hashCode(isDefaultVoiceSubscription); - result = 31 * result + Boolean.hashCode(isDefaultSmsSubscription); - result = 31 * result + Boolean.hashCode(isDefaultDataSubscription); - result = 31 * result + Boolean.hashCode(isDefaultSubscription); result = 31 * result + Boolean.hashCode(isActiveDataSubscriptionId); return result; } @@ -250,16 +223,11 @@ public class SubscriptionInfoEntity { && isSubscriptionVisible == info.isSubscriptionVisible && TextUtils.equals(formattedPhoneNumber, info.formattedPhoneNumber) && isFirstRemovableSubscription == info.isFirstRemovableSubscription - && TextUtils.equals(defaultSimConfig, info.defaultSimConfig) && isDefaultSubscriptionSelection == info.isDefaultSubscriptionSelection && isValidSubscription == info.isValidSubscription && isUsableSubscription == info.isUsableSubscription && isActiveSubscriptionId == info.isActiveSubscriptionId && isAvailableSubscription == info.isAvailableSubscription - && isDefaultVoiceSubscription == info.isDefaultVoiceSubscription - && isDefaultSmsSubscription == info.isDefaultSmsSubscription - && isDefaultDataSubscription == info.isDefaultDataSubscription - && isDefaultSubscription == info.isDefaultSubscription && isActiveDataSubscriptionId == info.isActiveDataSubscriptionId; } @@ -303,8 +271,6 @@ public class SubscriptionInfoEntity { .append(formattedPhoneNumber) .append(", isFirstRemovableSubscription = ") .append(isFirstRemovableSubscription) - .append(", defaultSimConfig = ") - .append(defaultSimConfig) .append(", isDefaultSubscriptionSelection = ") .append(isDefaultSubscriptionSelection) .append(", isValidSubscription = ") @@ -315,14 +281,6 @@ public class SubscriptionInfoEntity { .append(isActiveSubscriptionId) .append(", isAvailableSubscription = ") .append(isAvailableSubscription) - .append(", isDefaultVoiceSubscription = ") - .append(isDefaultVoiceSubscription) - .append(", isDefaultSmsSubscription = ") - .append(isDefaultSmsSubscription) - .append(", isDefaultDataSubscription = ") - .append(isDefaultDataSubscription) - .append(", isDefaultSubscription = ") - .append(isDefaultSubscription) .append(", isActiveDataSubscriptionId = ") .append(isActiveDataSubscriptionId) .append(")}"); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index e736253dc90f..5a8c59489ec8 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -2311,7 +2311,7 @@ public class SettingsProvider extends ContentProvider { @NonNull Set<String> flags) { boolean hasAllowlistPermission = context.checkCallingOrSelfPermission( - Manifest.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG) + Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG) == PackageManager.PERMISSION_GRANTED; boolean hasWritePermission = context.checkCallingOrSelfPermission( @@ -2331,7 +2331,7 @@ public class SettingsProvider extends ContentProvider { } } else { throw new SecurityException("Permission denial to mutate flag, must have root, " - + "WRITE_DEVICE_CONFIG, or ALLOWLISTED_WRITE_DEVICE_CONFIG"); + + "WRITE_DEVICE_CONFIG, or WRITE_ALLOWLISTED_DEVICE_CONFIG"); } } diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index f2f0fe987f36..19f1a86ec90c 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -273,7 +273,6 @@ public class SettingsBackupTest { Settings.Global.DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD, Settings.Global.SMART_REPLIES_IN_NOTIFICATIONS_FLAGS, Settings.Global.SMART_SUGGESTIONS_IN_NOTIFICATIONS_FLAGS, - Settings.Global.STYLUS_HANDWRITING_ENABLED, Settings.Global.STYLUS_EVER_USED, Settings.Global.ENABLE_ADB_INCREMENTAL_INSTALL_DEFAULT, Settings.Global.ENABLE_MULTI_SLOT_TIMEOUT_MILLIS, @@ -785,6 +784,7 @@ public class SettingsBackupTest { Settings.Secure.SMS_DEFAULT_APPLICATION, Settings.Secure.SPELL_CHECKER_ENABLED, // Intentionally removed in Q Settings.Secure.STYLUS_BUTTONS_ENABLED, + Settings.Secure.STYLUS_HANDWRITING_ENABLED, Settings.Secure.TRUST_AGENTS_INITIALIZED, Settings.Secure.KNOWN_TRUST_AGENTS_INITIALIZED, Settings.Secure.TV_APP_USES_NON_SYSTEM_INPUTS, diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 4c48f0e63b16..fedfb43535cc 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -148,7 +148,7 @@ <uses-permission android:name="android.permission.LOCATION_BYPASS" /> <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> - <uses-permission android:name="android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG" /> + <uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" /> <uses-permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml index 8d4431520c75..befbfab7dbc3 100644 --- a/packages/SystemUI/res/values/integers.xml +++ b/packages/SystemUI/res/values/integers.xml @@ -37,4 +37,12 @@ <dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen> <integer name="qs_carrier_max_em">7</integer> + + <!-- Maximum number of notification icons shown on the Always on Display + (excluding overflow dot) --> + <integer name="max_notif_icons_on_aod">3</integer> + <!-- Maximum number of notification icons shown on the lockscreen (excluding overflow dot) --> + <integer name="max_notif_icons_on_lockscreen">3</integer> + <!-- Maximum number of notification icons shown in the status bar (excluding overflow dot) --> + <integer name="max_notif_static_icons">4</integer> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 7ffb59422bc4..79a51d6670c4 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -701,6 +701,6 @@ object Flags { // TODO(b/272805037): Tracking Bug @JvmField - val ADVANCED_VPN_ENABLED = releasedFlag(2800, name = "AdvancedVpn__enable_feature", - namespace = "vpn") + val ADVANCED_VPN_ENABLED = unreleasedFlag(2800, name = "AdvancedVpn__enable_feature", + namespace = "vpn", teamfood = true) } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 2d7861be2da9..f5c0a94d07f2 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -174,21 +174,26 @@ constructor( infoReference.set(info) - // TODO(b/266686199): We should handle when app not available. For now, we log. - val intent = createNoteTaskIntent(info) try { + // TODO(b/266686199): We should handle when app not available. For now, we log. logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" } when (info.launchMode) { is NoteTaskLaunchMode.AppBubble -> { // TODO: provide app bubble icon + val intent = createNoteTaskIntent(info) bubbles.showOrHideAppBubble(intent, user, null /* icon */) // App bubble logging happens on `onBubbleExpandChanged`. logDebug { "onShowNoteTask - opened as app bubble: $info" } } is NoteTaskLaunchMode.Activity -> { if (activityManager.isInForeground(info.packageName)) { - logDebug { "onShowNoteTask - already opened as activity: $info" } + // Force note task into background by calling home. + val intent = createHomeIntent() + context.startActivityAsUser(intent, user) + eventLogger.logNoteTaskClosed(info) + logDebug { "onShowNoteTask - closed as activity: $info" } } else { + val intent = createNoteTaskIntent(info) context.startActivityAsUser(intent, user) eventLogger.logNoteTaskOpened(info) logDebug { "onShowNoteTask - opened as activity: $info" } @@ -199,7 +204,7 @@ constructor( } catch (e: ActivityNotFoundException) { logDebug { "onShowNoteTask - failed: $info" } } - logDebug { "onShowNoteTask - compoleted: $info" } + logDebug { "onShowNoteTask - completed: $info" } } /** @@ -306,3 +311,10 @@ private fun createNoteTaskIntent(info: NoteTaskInfo): Intent = private inline fun Any.logDebug(message: () -> String) { if (Build.IS_DEBUGGABLE) Log.d(this::class.java.simpleName.orEmpty(), message()) } + +/** Creates an [Intent] which forces the current app to background by calling home. */ +private fun createHomeIntent(): Intent = + Intent(Intent.ACTION_MAIN).apply { + addCategory(Intent.CATEGORY_HOME) + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 0b2ae05b7c9b..72286f175671 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -161,6 +161,10 @@ open class UserTrackerImpl internal constructor( private fun registerUserSwitchObserver() { iActivityManager.registerUserSwitchObserver(object : UserSwitchObserver() { + override fun onBeforeUserSwitching(newUserId: Int) { + setUserIdInternal(newUserId) + } + override fun onUserSwitching(newUserId: Int, reply: IRemoteCallback?) { backgroundHandler.run { handleUserSwitching(newUserId) @@ -181,8 +185,6 @@ open class UserTrackerImpl internal constructor( Assert.isNotMainThread() Log.i(TAG, "Switching to user $newUserId") - setUserIdInternal(newUserId) - val list = synchronized(callbacks) { callbacks.toList() } @@ -205,7 +207,6 @@ open class UserTrackerImpl internal constructor( Assert.isNotMainThread() Log.i(TAG, "Switched to user $newUserId") - setUserIdInternal(newUserId) notifySubscribers { onUserChanged(newUserId, userContext) onProfilesChanged(userProfiles) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java index 4ee2de11abdf..006a029de8e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java @@ -136,11 +136,13 @@ public class NotificationIconContainer extends ViewGroup { } }.setDuration(CONTENT_FADE_DURATION); - private static final int MAX_ICONS_ON_AOD = 3; + /* Maximum number of icons on AOD when also showing overflow dot. */ + private int mMaxIconsOnAod; /* Maximum number of icons in short shelf on lockscreen when also showing overflow dot. */ - public static final int MAX_ICONS_ON_LOCKSCREEN = 3; - public static final int MAX_STATIC_ICONS = 4; + private int mMaxIconsOnLockscreen; + /* Maximum number of icons in the status bar when also showing overflow dot. */ + private int mMaxStaticIcons; private boolean mIsStaticLayout = true; private final HashMap<View, IconState> mIconStates = new HashMap<>(); @@ -174,14 +176,19 @@ public class NotificationIconContainer extends ViewGroup { public NotificationIconContainer(Context context, AttributeSet attrs) { super(context, attrs); - initDimens(); + initResources(); setWillNotDraw(!(DEBUG || DEBUG_OVERFLOW)); } - private void initDimens() { + private void initResources() { + mMaxIconsOnAod = getResources().getInteger(R.integer.max_notif_icons_on_aod); + mMaxIconsOnLockscreen = getResources().getInteger(R.integer.max_notif_icons_on_lockscreen); + mMaxStaticIcons = getResources().getInteger(R.integer.max_notif_static_icons); + mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding); mStaticDotRadius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius); mStaticDotDiameter = 2 * mStaticDotRadius; + final Context themedContext = new ContextThemeWrapper(getContext(), com.android.internal.R.style.Theme_DeviceDefault_DayNight); mThemedTextColorPrimary = Utils.getColorAttr(themedContext, @@ -225,7 +232,7 @@ public class NotificationIconContainer extends ViewGroup { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - initDimens(); + initResources(); } @Override @@ -424,7 +431,7 @@ public class NotificationIconContainer extends ViewGroup { return 0f; } final float contentWidth = - mIconSize * MathUtils.min(numIcons, MAX_ICONS_ON_LOCKSCREEN + 1); + mIconSize * MathUtils.min(numIcons, mMaxIconsOnLockscreen + 1); return getActualPaddingStart() + contentWidth + getActualPaddingEnd(); @@ -539,8 +546,8 @@ public class NotificationIconContainer extends ViewGroup { } private int getMaxVisibleIcons(int childCount) { - return mOnLockScreen ? MAX_ICONS_ON_AOD : - mIsStaticLayout ? MAX_STATIC_ICONS : childCount; + return mOnLockScreen ? mMaxIconsOnAod : + mIsStaticLayout ? mMaxStaticIcons : childCount; } private float getLayoutEnd() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 53e08ea8e10d..118bfc55dd4c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -14,6 +14,7 @@ import android.view.WindowManager.fixScale import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD +import com.android.systemui.DejankUtils import com.android.systemui.animation.Interpolators import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardViewMediator @@ -27,6 +28,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator import com.android.systemui.statusbar.notification.stack.AnimationProperties import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.util.TraceUtils import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject @@ -116,6 +118,11 @@ class UnlockedScreenOffAnimationController @Inject constructor( }) } + // FrameCallback used to delay starting the light reveal animation until the next frame + private val startLightRevealCallback = TraceUtils.namedRunnable("startLightReveal") { + lightRevealAnimator.start() + } + val animatorDurationScaleObserver = object : ContentObserver(null) { override fun onChange(selfChange: Boolean) { updateAnimatorDurationScale() @@ -223,6 +230,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( decidedToAnimateGoingToSleep = null shouldAnimateInKeyguard = false + DejankUtils.removeCallbacks(startLightRevealCallback) lightRevealAnimator.cancel() handler.removeCallbacksAndMessages(null) } @@ -253,7 +261,14 @@ class UnlockedScreenOffAnimationController @Inject constructor( shouldAnimateInKeyguard = true lightRevealAnimationPlaying = true - lightRevealAnimator.start() + + // Start the animation on the next frame. startAnimation() is called after + // PhoneWindowManager makes a binder call to System UI on + // IKeyguardService#onStartedGoingToSleep(). By the time we get here, system_server is + // already busy making changes to PowerManager and DisplayManager. This increases our + // chance of missing the first frame, so to mitigate this we should start the animation + // on the next frame. + DejankUtils.postAfterTraversal(startLightRevealCallback) handler.postDelayed({ // Only run this callback if the device is sleeping (not interactive). This callback // is removed in onStartedWakingUp, but since that event is asynchronously diff --git a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt index b311318fb111..64234c205617 100644 --- a/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt +++ b/packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt @@ -17,6 +17,7 @@ package com.android.systemui.util import android.os.Trace +import android.os.TraceNameSupplier /** * Run a block within a [Trace] section. @@ -39,5 +40,16 @@ class TraceUtils { inline fun traceRunnable(tag: String, crossinline block: () -> Unit): Runnable { return Runnable { traceSection(tag) { block() } } } + + /** + * Helper function for creating a Runnable object that implements TraceNameSupplier. + * This is useful for posting Runnables to Handlers with meaningful names. + */ + inline fun namedRunnable(tag: String, crossinline block: () -> Unit): Runnable { + return object : Runnable, TraceNameSupplier { + override fun getTraceName(): String = tag + override fun run() = block() + } + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index 9897ce106137..fbe089a0616f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -25,6 +25,8 @@ import android.app.role.RoleManager.ROLE_NOTES import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.Intent.ACTION_MAIN +import android.content.Intent.CATEGORY_HOME import android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT import android.content.Intent.FLAG_ACTIVITY_NEW_TASK @@ -278,7 +280,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test - fun showNoteTask_keyguardIsLocked_noteIsOpen_shouldStartActivityAndLogUiEvent() { + fun showNoteTask_keyguardIsLocked_noteIsOpen_shouldCloseActivityAndLogUiEvent() { val expectedInfo = NOTE_TASK_INFO.copy( entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, @@ -291,8 +293,17 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().showNoteTask(entryPoint = expectedInfo.entryPoint!!) - verify(context, never()).startActivityAsUser(any(), any()) - verifyZeroInteractions(bubbles, eventLogger) + val intentCaptor = argumentCaptor<Intent>() + val userCaptor = argumentCaptor<UserHandle>() + verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) + intentCaptor.value.let { intent -> + assertThat(intent.action).isEqualTo(ACTION_MAIN) + assertThat(intent.categories).contains(CATEGORY_HOME) + assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK) + } + assertThat(userCaptor.value).isEqualTo(userTracker.userHandle) + verify(eventLogger).logNoteTaskClosed(expectedInfo) + verifyZeroInteractions(bubbles) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt index 71ba21538a8e..aa98f08e9015 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt @@ -167,6 +167,7 @@ class UserTrackerImplTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + captor.value.onBeforeUserSwitching(newID) captor.value.onUserSwitching(newID, userSwitchingReply) verify(userSwitchingReply).sendResult(any()) @@ -290,6 +291,7 @@ class UserTrackerImplTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + captor.value.onBeforeUserSwitching(newID) captor.value.onUserSwitching(newID, userSwitchingReply) verify(userSwitchingReply).sendResult(any()) @@ -308,6 +310,7 @@ class UserTrackerImplTest : SysuiTestCase() { val captor = ArgumentCaptor.forClass(IUserSwitchObserver::class.java) verify(iActivityManager).registerUserSwitchObserver(capture(captor), anyString()) + captor.value.onBeforeUserSwitching(newID) captor.value.onUserSwitchComplete(newID) assertThat(callback.calledOnUserChanged).isEqualTo(1) diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index cc3b4ab0fb4e..652052736bed 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -57,6 +57,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; import android.content.pm.UserInfo; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -100,6 +101,7 @@ import com.android.systemui.shade.ShadeController; import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.ShadeWindowLogger; import com.android.systemui.shared.system.QuickStepContract; +import com.android.systemui.statusbar.NotificationEntryHelper; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; import com.android.systemui.statusbar.SysuiStatusBarStateController; @@ -135,6 +137,7 @@ import com.android.wm.shell.bubbles.BubbleLogger; import com.android.wm.shell.bubbles.BubbleStackView; import com.android.wm.shell.bubbles.BubbleViewInfoTask; import com.android.wm.shell.bubbles.Bubbles; +import com.android.wm.shell.bubbles.StackEducationViewKt; import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.ShellExecutor; @@ -1669,6 +1672,60 @@ public class BubblesTest extends SysuiTestCase { } @Test + public void testShowStackEdu_isNotConversationBubble() { + // Setup + setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false); + BubbleEntry bubbleEntry = createBubbleEntry(false /* isConversation */); + mBubbleController.updateBubble(bubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + + // Click on bubble + Bubble bubble = mBubbleData.getBubbleInStackWithKey(bubbleEntry.getKey()); + assertFalse(bubble.isConversation()); + bubble.getIconView().callOnClick(); + + // Check education is not shown + BubbleStackView stackView = mBubbleController.getStackView(); + assertFalse(stackView.isStackEduVisible()); + } + + @Test + public void testShowStackEdu_isConversationBubble() { + // Setup + setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, false); + BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */); + mBubbleController.updateBubble(bubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + + // Click on bubble + Bubble bubble = mBubbleData.getBubbleInStackWithKey(bubbleEntry.getKey()); + assertTrue(bubble.isConversation()); + bubble.getIconView().callOnClick(); + + // Check education is shown + BubbleStackView stackView = mBubbleController.getStackView(); + assertTrue(stackView.isStackEduVisible()); + } + + @Test + public void testShowStackEdu_isSeenConversationBubble() { + // Setup + setPrefBoolean(StackEducationViewKt.PREF_STACK_EDUCATION, true); + BubbleEntry bubbleEntry = createBubbleEntry(true /* isConversation */); + mBubbleController.updateBubble(bubbleEntry); + assertTrue(mBubbleController.hasBubbles()); + + // Click on bubble + Bubble bubble = mBubbleData.getBubbleInStackWithKey(bubbleEntry.getKey()); + assertTrue(bubble.isConversation()); + bubble.getIconView().callOnClick(); + + // Check education is not shown + BubbleStackView stackView = mBubbleController.getStackView(); + assertFalse(stackView.isStackEduVisible()); + } + + @Test public void testShowOrHideAppBubble_addsAndExpand() { assertThat(mBubbleController.isStackExpanded()).isFalse(); assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull(); @@ -1816,6 +1873,20 @@ public class BubblesTest extends SysuiTestCase { mock(Bubbles.PendingIntentCanceledListener.class), new SyncExecutor()); } + private BubbleEntry createBubbleEntry(boolean isConversation) { + NotificationEntry notificationEntry = mNotificationTestHelper.createBubble(mDeleteIntent); + if (isConversation) { + ShortcutInfo shortcutInfo = new ShortcutInfo.Builder(mContext) + .setId("shortcutId") + .build(); + NotificationEntryHelper.modifyRanking(notificationEntry) + .setIsConversation(true) + .setShortcutInfo(shortcutInfo) + .build(); + } + return mBubblesManager.notifToBubbleEntry(notificationEntry); + } + /** Creates a context that will return a PackageManager with specific AppInfo. */ private Context setUpContextWithPackageManager(String pkg, ApplicationInfo info) throws Exception { @@ -1852,6 +1923,15 @@ public class BubblesTest extends SysuiTestCase { bubbleMetadata.setFlags(flags); } + /** + * Set preferences boolean value for key + * Used to setup global state for stack view education tests + */ + private void setPrefBoolean(String key, boolean enabled) { + mContext.getSharedPreferences(mContext.getPackageName(), Context.MODE_PRIVATE) + .edit().putBoolean(key, enabled).apply(); + } + private Notification.BubbleMetadata getMetadata() { Intent target = new Intent(mContext, BubblesTestActivity.class); PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, FLAG_MUTABLE); diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index 990dd64434a0..484e9566b036 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -338,9 +338,7 @@ class InputController { } synchronized (mLock) { - InputDeviceDescriptor[] values = mInputDeviceDescriptors.values().toArray( - new InputDeviceDescriptor[0]); - for (InputDeviceDescriptor value : values) { + for (InputDeviceDescriptor value : mInputDeviceDescriptors.values()) { if (value.mName.equals(deviceName)) { throw new DeviceCreationException( "Input device name already in use: " + deviceName); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 713b993824f9..8d8ed196be18 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -38,6 +38,8 @@ import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.StopUserOnSwitch; +import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_FROZEN; +import static android.app.ActivityManager.UidFrozenStateChangedCallback.UID_FROZEN_STATE_UNFROZEN; import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityManagerInternal.MEDIA_PROJECTION_TOKEN_EVENT_CREATED; @@ -7500,9 +7502,9 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void registerUidFrozenStateChangedCallback( @NonNull IUidFrozenStateChangedCallback callback) { + Preconditions.checkNotNull(callback, "callback cannot be null"); enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, "registerUidFrozenStateChangedCallback()"); - Preconditions.checkNotNull(callback, "callback cannot be null"); synchronized (mUidFrozenStateChangedCallbackList) { final boolean registered = mUidFrozenStateChangedCallbackList.register(callback); if (!registered) { @@ -7520,15 +7522,48 @@ public class ActivityManagerService extends IActivityManager.Stub @Override public void unregisterUidFrozenStateChangedCallback( @NonNull IUidFrozenStateChangedCallback callback) { + Preconditions.checkNotNull(callback, "callback cannot be null"); enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, "unregisterUidFrozenStateChangedCallback()"); - Preconditions.checkNotNull(callback, "callback cannot be null"); synchronized (mUidFrozenStateChangedCallbackList) { mUidFrozenStateChangedCallbackList.unregister(callback); } } /** + * Query the frozen state of a list of UIDs. + * + * @param uids the array of UIDs which the client would like to know the frozen state of. + * @return An array containing the frozen state for each requested UID, by index. Will be set + * to {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_FROZEN} + * if the UID is frozen. If the UID is not frozen or not found, + * {@link UidFrozenStateChangedCallback#UID_FROZEN_STATE_UNFROZEN} + * will be set. + * + * @hide + */ + @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS) + @Override + public @NonNull int[] getUidFrozenState(@NonNull int[] uids) { + Preconditions.checkNotNull(uids, "uid array cannot be null"); + enforceCallingPermission(android.Manifest.permission.PACKAGE_USAGE_STATS, + "getUidFrozenState()"); + + final int[] frozenStates = new int[uids.length]; + synchronized (mProcLock) { + for (int i = 0; i < uids.length; i++) { + final UidRecord uidRec = mProcessList.mActiveUids.get(uids[i]); + if (uidRec != null && uidRec.areAllProcessesFrozen()) { + frozenStates[i] = UID_FROZEN_STATE_FROZEN; + } else { + frozenStates[i] = UID_FROZEN_STATE_UNFROZEN; + } + } + } + return frozenStates; + } + + /** * Notify the system that a UID has been frozen or unfrozen. * * @param uids The Uid(s) in question diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 1b378837e558..d926c2c7c7a8 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -2141,6 +2141,17 @@ class UserController implements Handler.Callback { final int observerCount = mUserSwitchObservers.beginBroadcast(); if (observerCount > 0) { + for (int i = 0; i < observerCount; i++) { + final String name = "#" + i + " " + mUserSwitchObservers.getBroadcastCookie(i); + t.traceBegin("onBeforeUserSwitching-" + name); + try { + mUserSwitchObservers.getBroadcastItem(i).onBeforeUserSwitching(newUserId); + } catch (RemoteException e) { + // Ignore + } finally { + t.traceEnd(); + } + } final ArraySet<String> curWaitingUserSwitchCallbacks = new ArraySet<>(); synchronized (mLock) { uss.switching = true; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 7a0bf0cacdfb..b440208e3e32 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -20,6 +20,8 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; import static android.os.IServiceManager.DUMP_FLAG_PROTO; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.provider.Settings.Secure.STYLUS_HANDWRITING_DEFAULT_VALUE; +import static android.provider.Settings.Secure.STYLUS_HANDWRITING_ENABLED; import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION; import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD; import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE; @@ -2067,10 +2069,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } synchronized (ImfLock.class) { + if (!isStylusHandwritingEnabled(mContext, userId)) { + return false; + } + + // Check if selected IME of current user supports handwriting. if (userId == mSettings.getCurrentUserId()) { return mBindingController.supportsStylusHandwriting(); } - //TODO(b/197848765): This can be optimized by caching multi-user methodMaps/methodList. //TODO(b/210039666): use cache. final ArrayMap<String, InputMethodInfo> methodMap = queryMethodMapForUser(userId); @@ -2081,6 +2087,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } + private boolean isStylusHandwritingEnabled( + @NonNull Context context, @UserIdInt int userId) { + // If user is a profile, use preference of it`s parent profile. + final int profileParentUserId = mUserManagerInternal.getProfileParentId(userId); + if (Settings.Secure.getIntForUser(context.getContentResolver(), + STYLUS_HANDWRITING_ENABLED, STYLUS_HANDWRITING_DEFAULT_VALUE, + profileParentUserId) == 0) { + return false; + } + return true; + } + @GuardedBy("ImfLock.class") private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId, @DirectBootAwareness int directBootAwareness, int callingUid) { @@ -3418,8 +3436,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void prepareStylusHandwritingDelegation( @NonNull IInputMethodClient client, + @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName) { + if (!isStylusHandwritingEnabled(mContext, userId)) { + Slog.w(TAG, "Can not prepare stylus handwriting delegation. Stylus handwriting" + + " pref is disabled for user: " + userId); + return; + } if (!verifyClientAndPackageMatch(client, delegatorPackageName)) { Slog.w(TAG, "prepareStylusHandwritingDelegation() fail"); throw new IllegalArgumentException("Delegator doesn't match Uid"); @@ -3430,8 +3454,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public boolean acceptStylusHandwritingDelegation( @NonNull IInputMethodClient client, + @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName) { + if (!isStylusHandwritingEnabled(mContext, userId)) { + Slog.w(TAG, "Can not accept stylus handwriting delegation. Stylus handwriting" + + " pref is disabled for user: " + userId); + return false; + } if (!verifyDelegator(client, delegatePackageName, delegatorPackageName)) { return false; } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index a4eb417be4e1..65dcec702ef4 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -3323,7 +3323,8 @@ public class NotificationManagerService extends SystemService { final boolean isSystemToast = isCallerSystemOrPhone() || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg); boolean isAppRenderedToast = (callback != null); - if (!checkCanEnqueueToast(pkg, callingUid, isAppRenderedToast, isSystemToast)) { + if (!checkCanEnqueueToast(pkg, callingUid, displayId, isAppRenderedToast, + isSystemToast)) { return; } @@ -3393,7 +3394,7 @@ public class NotificationManagerService extends SystemService { } } - private boolean checkCanEnqueueToast(String pkg, int callingUid, + private boolean checkCanEnqueueToast(String pkg, int callingUid, int displayId, boolean isAppRenderedToast, boolean isSystemToast) { final boolean isPackageSuspended = isPackagePaused(pkg); final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg, @@ -3423,6 +3424,13 @@ public class NotificationManagerService extends SystemService { return false; } + int userId = UserHandle.getUserId(callingUid); + if (!isSystemToast && !mUmInternal.isUserVisible(userId, displayId)) { + Slog.e(TAG, "Suppressing toast from package " + pkg + "/" + callingUid + " as user " + + userId + " is not visible on display " + displayId); + return false; + } + return true; } diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index acd4a96c2817..6f7ce80e42b1 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -571,6 +571,7 @@ public class ComputerEngine implements Computer { if (!blockInstantResolution && !blockNormalResolution) { final ResolveInfo ri = new ResolveInfo(); ri.activityInfo = ai; + ri.userHandle = UserHandle.of(userId); list = new ArrayList<>(1); list.add(ri); PackageManagerServiceUtils.applyEnforceIntentFilterMatching( diff --git a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java index f653e4b26438..6cb6dc07f8b8 100644 --- a/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java +++ b/services/core/java/com/android/server/stats/pull/ProcfsMemoryUtil.java @@ -28,7 +28,8 @@ public final class ProcfsMemoryUtil { "VmHWM:", "VmRSS:", "RssAnon:", - "VmSwap:" + "RssShmem:", + "VmSwap:", }; private static final String[] VMSTAT_KEYS = new String[] { "oom_kill" @@ -38,7 +39,7 @@ public final class ProcfsMemoryUtil { /** * Reads memory stats of a process from procfs. Returns values of the VmHWM, VmRss, AnonRSS, - * VmSwap fields in /proc/pid/status in kilobytes or null if not available. + * VmSwap, RssShmem fields in /proc/pid/status in kilobytes or null if not available. */ @Nullable public static MemorySnapshot readMemorySnapshotFromProcfs(int pid) { @@ -46,8 +47,9 @@ public final class ProcfsMemoryUtil { output[0] = -1; output[3] = -1; output[4] = -1; + output[5] = -1; Process.readProcLines("/proc/" + pid + "/status", STATUS_KEYS, output); - if (output[0] == -1 || output[3] == -1 || output[4] == -1) { + if (output[0] == -1 || output[3] == -1 || output[4] == -1 || output[5] == -1) { // Could not open or parse file. return null; } @@ -56,7 +58,8 @@ public final class ProcfsMemoryUtil { snapshot.rssHighWaterMarkInKilobytes = (int) output[1]; snapshot.rssInKilobytes = (int) output[2]; snapshot.anonRssInKilobytes = (int) output[3]; - snapshot.swapInKilobytes = (int) output[4]; + snapshot.rssShmemKilobytes = (int) output[4]; + snapshot.swapInKilobytes = (int) output[5]; return snapshot; } @@ -101,6 +104,7 @@ public final class ProcfsMemoryUtil { public int rssInKilobytes; public int anonRssInKilobytes; public int swapInKilobytes; + public int rssShmemKilobytes; } /** Reads and parses selected entries of /proc/vmstat. */ 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 f8a4b04180c3..b2f48d9e3d8c 100644 --- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java +++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java @@ -2290,7 +2290,8 @@ public class StatsPullAtomService extends SystemService { managedProcess.processName, managedProcess.pid, managedProcess.oomScore, snapshot.rssInKilobytes, snapshot.anonRssInKilobytes, snapshot.swapInKilobytes, snapshot.anonRssInKilobytes + snapshot.swapInKilobytes, - gpuMemPerPid.get(managedProcess.pid), managedProcess.hasForegroundServices)); + gpuMemPerPid.get(managedProcess.pid), managedProcess.hasForegroundServices, + snapshot.rssShmemKilobytes)); } // Complement the data with native system processes. Given these measurements can be taken // in response to LMKs happening, we want to first collect the managed app stats (to @@ -2309,7 +2310,8 @@ public class StatsPullAtomService extends SystemService { -1001 /*Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.*/, snapshot.rssInKilobytes, snapshot.anonRssInKilobytes, snapshot.swapInKilobytes, snapshot.anonRssInKilobytes + snapshot.swapInKilobytes, - gpuMemPerPid.get(pid), false /* has_foreground_services */)); + gpuMemPerPid.get(pid), false /* has_foreground_services */, + snapshot.rssShmemKilobytes)); } return StatsManager.PULL_SUCCESS; } diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java index 95fd82ff1154..80d1e1683de1 100644 --- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java +++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java @@ -398,6 +398,7 @@ class ActivityMetricsLogger { /** Returns {@code true} if the incoming activity can belong to this transition. */ boolean canCoalesce(ActivityRecord r) { return mLastLaunchedActivity.mDisplayContent == r.mDisplayContent + && mLastLaunchedActivity.getTask().getBounds().equals(r.getTask().getBounds()) && mLastLaunchedActivity.getWindowingMode() == r.getWindowingMode(); } @@ -646,7 +647,7 @@ class ActivityMetricsLogger { void notifyActivityLaunched(@NonNull LaunchingState launchingState, int resultCode, boolean newActivityCreated, @Nullable ActivityRecord launchedActivity, @Nullable ActivityOptions options) { - if (launchedActivity == null) { + if (launchedActivity == null || launchedActivity.getTask() == null) { // The launch is aborted, e.g. intent not resolved, class not found. abort(launchingState, "nothing launched"); return; diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index f8fb76acf81e..7c1e9071b926 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -574,7 +574,9 @@ public class ActivityStartController { mService.deferWindowLayout(); try { final TransitionController controller = r.mTransitionController; - if (controller.getTransitionPlayer() != null) { + final Transition transition = controller.getCollectingTransition(); + if (transition != null) { + transition.setRemoteAnimationApp(r.app.getThread()); controller.collect(task); controller.setTransientLaunch(r, TaskDisplayArea.getRootTaskAbove(rootTask)); } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 7b9cc6fee602..bbdaa24a694c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2011,6 +2011,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return; } + if (r == mRootWindowContainer.getTopResumedActivity()) { + setLastResumedActivityUncheckLocked(r, "setFocusedTask-alreadyTop"); + return; + } final Transition transition = (getTransitionController().isCollecting() || !getTransitionController().isShellTransitionsEnabled()) ? null : getTransitionController().createTransition(TRANSIT_TO_FRONT); @@ -4788,11 +4792,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { // until we've committed to the gesture. The focus will be transferred at the end of // the transition (if the transient launch is committed) or early if explicitly requested // via `setFocused*`. + boolean focusedAppChanged = false; if (!getTransitionController().isTransientCollect(r)) { - final Task prevFocusTask = r.mDisplayContent.mFocusedApp != null - ? r.mDisplayContent.mFocusedApp.getTask() : null; - final boolean changed = r.mDisplayContent.setFocusedApp(r); - if (changed) { + focusedAppChanged = r.mDisplayContent.setFocusedApp(r); + if (focusedAppChanged) { mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); } @@ -4801,13 +4804,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mTaskSupervisor.mRecentTasks.add(task); } - applyUpdateLockStateLocked(r); - applyUpdateVrModeLocked(r); + if (focusedAppChanged) { + applyUpdateLockStateLocked(r); + } + if (mVrController.mVrService != null) { + applyUpdateVrModeLocked(r); + } - EventLogTags.writeWmSetResumedActivity( - r == null ? -1 : r.mUserId, - r == null ? "NULL" : r.shortComponentName, - reason); + EventLogTags.writeWmSetResumedActivity(r.mUserId, r.shortComponentName, reason); } final class SleepTokenAcquirerImpl implements ActivityTaskManagerInternal.SleepTokenAcquirer { diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 1604c2a0343b..a44f25ca8051 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -3300,7 +3300,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Unlink death from remote to clear the reference from binder -> mRemoteInsetsDeath // -> this DisplayContent. setRemoteInsetsController(null); - mWmService.mAnimator.removeDisplayLocked(mDisplayId); mOverlayLayer.release(); mA11yOverlayLayer.release(); mWindowingLayer.release(); @@ -5312,8 +5311,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // {@link DisplayContent} ready for use. mDisplayReady = true; - mWmService.mAnimator.addDisplayLocked(mDisplayId); - if (mWmService.mDisplayManagerInternal != null) { mWmService.mDisplayManagerInternal .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo()); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 8fa7592e6da0..951a71d2ddb9 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -66,6 +66,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.IApplicationThread; import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; @@ -83,7 +84,6 @@ import android.util.SparseArray; import android.view.Display; import android.view.SurfaceControl; import android.view.WindowManager; -import android.window.RemoteTransition; import android.window.ScreenCapture; import android.window.TransitionInfo; @@ -160,7 +160,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { private final TransitionController mController; private final BLASTSyncEngine mSyncEngine; private final Token mToken; - private RemoteTransition mRemoteTransition = null; + private IApplicationThread mRemoteAnimApp; /** Only use for clean-up after binder death! */ private SurfaceControl.Transaction mStartTransaction = null; @@ -1075,12 +1075,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { return mForcePlaying; } - void setRemoteTransition(RemoteTransition remoteTransition) { - mRemoteTransition = remoteTransition; + void setRemoteAnimationApp(IApplicationThread app) { + mRemoteAnimApp = app; } - RemoteTransition getRemoteTransition() { - return mRemoteTransition; + /** Returns the app which will run the transition animation. */ + IApplicationThread getRemoteAnimationApp() { + return mRemoteAnimApp; } void setNoAnimation(WindowContainer wc) { diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 7e267e47ede3..bcb8c46de5ed 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -567,7 +567,9 @@ class TransitionController { transition.mLogger.mRequestTimeNs = SystemClock.elapsedRealtimeNanos(); transition.mLogger.mRequest = request; mTransitionPlayer.requestStartTransition(transition.getToken(), request); - transition.setRemoteTransition(remoteTransition); + if (remoteTransition != null) { + transition.setRemoteAnimationApp(remoteTransition.getAppThread()); + } } catch (RemoteException e) { Slog.e(TAG, "Error requesting transition", e); transition.start(); @@ -779,9 +781,8 @@ class TransitionController { mRemotePlayer.clear(); return; } - final RemoteTransition remote = transition.getRemoteTransition(); - if (remote == null) return; - final IApplicationThread appThread = remote.getAppThread(); + final IApplicationThread appThread = transition.getRemoteAnimationApp(); + if (appThread == null || appThread == mTransitionPlayerProc.getThread()) return; final WindowProcessController delegate = mAtm.getProcessController(appThread); if (delegate == null) return; mRemotePlayer.update(delegate, isPlaying, true /* predict */); diff --git a/services/core/java/com/android/server/wm/VrController.java b/services/core/java/com/android/server/wm/VrController.java index 9e159aba4d77..241a8ae88ae7 100644 --- a/services/core/java/com/android/server/wm/VrController.java +++ b/services/core/java/com/android/server/wm/VrController.java @@ -126,6 +126,9 @@ final class VrController { } }; + /** If it is null after system ready, then VR mode is not supported. */ + VrManagerInternal mVrService; + /** * Create new VrController instance. * @@ -141,6 +144,7 @@ final class VrController { public void onSystemReady() { VrManagerInternal vrManagerInternal = LocalServices.getService(VrManagerInternal.class); if (vrManagerInternal != null) { + mVrService = vrManagerInternal; vrManagerInternal.addPersistentVrModeStateListener(mPersistentVrModeListener); } } @@ -181,7 +185,7 @@ final class VrController { public boolean onVrModeChanged(ActivityRecord record) { // This message means that the top focused activity enabled VR mode (or an activity // that previously set this has become focused). - VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); + final VrManagerInternal vrService = mVrService; if (vrService == null) { // VR mode isn't supported on this device. return false; diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java index 10bedd4b921f..adc0595f305b 100644 --- a/services/core/java/com/android/server/wm/WindowAnimator.java +++ b/services/core/java/com/android/server/wm/WindowAnimator.java @@ -30,7 +30,6 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.content.Context; import android.os.Trace; import android.util.Slog; -import android.util.SparseArray; import android.util.TimeUtils; import android.view.Choreographer; import android.view.SurfaceControl; @@ -66,7 +65,6 @@ public class WindowAnimator { int mBulkUpdateParams = 0; Object mLastWindowFreezeSource; - SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators = new SparseArray<>(2); private boolean mInitialized = false; private Choreographer mChoreographer; @@ -98,8 +96,7 @@ public class WindowAnimator { mAnimationFrameCallback = frameTimeNs -> { synchronized (mService.mGlobalLock) { mAnimationFrameCallbackScheduled = false; - final long vsyncId = mChoreographer.getVsyncId(); - animate(frameTimeNs, vsyncId); + animate(frameTimeNs); if (mNotifyWhenNoAnimation && !mLastRootAnimating) { mService.mGlobalLock.notifyAll(); } @@ -107,21 +104,11 @@ public class WindowAnimator { }; } - void addDisplayLocked(final int displayId) { - // Create the DisplayContentsAnimator object by retrieving it if the associated - // {@link DisplayContent} exists. - getDisplayContentsAnimatorLocked(displayId); - } - - void removeDisplayLocked(final int displayId) { - mDisplayContentsAnimators.delete(displayId); - } - void ready() { mInitialized = true; } - private void animate(long frameTimeNs, long vsyncId) { + private void animate(long frameTimeNs) { if (!mInitialized) { return; } @@ -145,10 +132,9 @@ public class WindowAnimator { final AccessibilityController accessibilityController = mService.mAccessibilityController; - final int numDisplays = mDisplayContentsAnimators.size(); + final int numDisplays = root.getChildCount(); for (int i = 0; i < numDisplays; i++) { - final int displayId = mDisplayContentsAnimators.keyAt(i); - final DisplayContent dc = root.getDisplayContent(displayId); + final DisplayContent dc = root.getChildAt(i); // Update animations of all applications, including those associated with // exiting/removed apps. dc.updateWindowsForAnimator(); @@ -156,12 +142,11 @@ public class WindowAnimator { } for (int i = 0; i < numDisplays; i++) { - final int displayId = mDisplayContentsAnimators.keyAt(i); - final DisplayContent dc = root.getDisplayContent(displayId); + final DisplayContent dc = root.getChildAt(i); dc.checkAppWindowsReadyToShow(); if (accessibilityController.hasCallbacks()) { - accessibilityController.drawMagnifiedRegionBorderIfNeeded(displayId, + accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId, mTransaction); } } @@ -237,12 +222,9 @@ public class WindowAnimator { public void dumpLocked(PrintWriter pw, String prefix, boolean dumpAll) { final String subPrefix = " " + prefix; - for (int i = 0; i < mDisplayContentsAnimators.size(); i++) { - pw.print(prefix); pw.print("DisplayContentsAnimator #"); - pw.print(mDisplayContentsAnimators.keyAt(i)); - pw.println(":"); - final DisplayContent dc = - mService.mRoot.getDisplayContent(mDisplayContentsAnimators.keyAt(i)); + for (int i = 0; i < mService.mRoot.getChildCount(); i++) { + final DisplayContent dc = mService.mRoot.getChildAt(i); + pw.print(prefix); pw.print(dc); pw.println(":"); dc.dumpWindowAnimators(pw, subPrefix); pw.println(); } @@ -260,23 +242,6 @@ public class WindowAnimator { } } - private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) { - if (displayId < 0) { - return null; - } - - DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId); - - // It is possible that this underlying {@link DisplayContent} has been removed. In this - // case, we do not want to create an animator associated with it as {link #animate} will - // fail. - if (displayAnimator == null && mService.mRoot.getDisplayContent(displayId) != null) { - displayAnimator = new DisplayContentsAnimator(); - mDisplayContentsAnimators.put(displayId, displayAnimator); - } - return displayAnimator; - } - void scheduleAnimation() { if (!mAnimationFrameCallbackScheduled) { mAnimationFrameCallbackScheduled = true; @@ -291,9 +256,6 @@ public class WindowAnimator { } } - private class DisplayContentsAnimator { - } - boolean isAnimationScheduled() { return mAnimationFrameCallbackScheduled; } diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java index e65b9f39e31c..531a6bdc0130 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java @@ -160,12 +160,10 @@ public final class CredentialManagerService int resolvedUserId, boolean disabled, String[] serviceNames) { getOrConstructSystemServiceListLock(resolvedUserId); if (serviceNames == null || serviceNames.length == 0) { - Slog.i(TAG, "serviceNames sent in newServiceListLocked is null, or empty"); return new ArrayList<>(); } List<CredentialManagerServiceImpl> serviceList = new ArrayList<>(serviceNames.length); for (String serviceName : serviceNames) { - Log.i(TAG, "in newServiceListLocked, service: " + serviceName); if (TextUtils.isEmpty(serviceName)) { continue; } @@ -173,7 +171,7 @@ public final class CredentialManagerService serviceList.add( new CredentialManagerServiceImpl(this, mLock, resolvedUserId, serviceName)); } catch (PackageManager.NameNotFoundException | SecurityException e) { - Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage()); + Slog.e(TAG, "Unable to add serviceInfo : ", e); } } return serviceList; @@ -423,7 +421,7 @@ public final class CredentialManagerService userId); callingAppInfo = new CallingAppInfo(realPackageName, packageInfo.signingInfo, origin); } catch (PackageManager.NameNotFoundException e) { - Log.i(TAG, "Issue while retrieving signatureInfo : " + e.getMessage()); + Slog.e(TAG, "Issue while retrieving signatureInfo : ", e); callingAppInfo = new CallingAppInfo(realPackageName, null, origin); } return callingAppInfo; @@ -436,7 +434,8 @@ public final class CredentialManagerService IGetCredentialCallback callback, final String callingPackage) { final long timestampBegan = System.nanoTime(); - Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage); + Slog.d(TAG, "starting executeGetCredential with callingPackage: " + + callingPackage); ICancellationSignal cancelTransport = CancellationSignal.createTransport(); if (request.getOrigin() != null) { @@ -630,11 +629,10 @@ public final class CredentialManagerService GetCredentialException.TYPE_NO_CREDENTIAL, "No credentials available on this device."); } catch (RemoteException e) { - Log.i( + Slog.e( TAG, "Issue invoking onError on IGetCredentialCallback " - + "callback: " - + e.getMessage()); + + "callback: ", e); } } @@ -649,7 +647,7 @@ public final class CredentialManagerService ICreateCredentialCallback callback, String callingPackage) { final long timestampBegan = System.nanoTime(); - Log.i(TAG, "starting executeCreateCredential with callingPackage: " + Slog.d(TAG, "starting executeCreateCredential with callingPackage: " + callingPackage); ICancellationSignal cancelTransport = CancellationSignal.createTransport(); @@ -692,11 +690,10 @@ public final class CredentialManagerService CreateCredentialException.TYPE_NO_CREATE_OPTIONS, "No create options available."); } catch (RemoteException e) { - Log.i( + Slog.e( TAG, "Issue invoking onError on ICreateCredentialCallback " - + "callback: " - + e.getMessage()); + + "callback: ", e); } } @@ -712,21 +709,19 @@ public final class CredentialManagerService MetricUtilities.logApiCalledInitialPhase(initMetric, session.mRequestSessionMetric.returnIncrementSequence()); } catch (Exception e) { - Log.w(TAG, "Unexpected error during metric logging: " + e); + Log.w(TAG, "Unexpected error during metric logging: ", e); } } @Override public void setEnabledProviders( List<String> providers, int userId, ISetEnabledProvidersCallback callback) { - Log.i(TAG, "setEnabledProviders"); - if (!hasWriteSecureSettingsPermission()) { try { callback.onError( PERMISSION_DENIED_ERROR, PERMISSION_DENIED_WRITE_SECURE_SETTINGS_ERROR); } catch (RemoteException e) { - Log.e(TAG, "Issue with invoking response: " + e.getMessage()); + Slog.e(TAG, "Issue with invoking response: ", e); } return; } @@ -753,7 +748,7 @@ public final class CredentialManagerService "failed_setting_store", "Failed to store setting containing enabled providers"); } catch (RemoteException e) { - Log.i(TAG, "Issue with invoking error response: " + e.getMessage()); + Slog.e(TAG, "Issue with invoking error response: ", e); return; } } @@ -762,7 +757,7 @@ public final class CredentialManagerService try { callback.onResponse(); } catch (RemoteException e) { - Log.i(TAG, "Issue with invoking response: " + e.getMessage()); + Slog.e(TAG, "Issue with invoking response: ", e); // TODO: Propagate failure } @@ -774,7 +769,8 @@ public final class CredentialManagerService @Override public boolean isEnabledCredentialProviderService( ComponentName componentName, String callingPackage) { - Log.i(TAG, "isEnabledCredentialProviderService"); + Slog.d(TAG, "isEnabledCredentialProviderService with componentName: " + + componentName.flattenToString()); // TODO(253157366): Check additional set of services. final int userId = UserHandle.getCallingUserId(); @@ -792,10 +788,10 @@ public final class CredentialManagerService MetricUtilities.logApiCalledSimpleV1( ApiName.IS_ENABLED_CREDENTIAL_PROVIDER_SERVICE, ApiStatus.FAILURE, callingUid); - Log.w( + Slog.w( TAG, - "isEnabledCredentialProviderService: Component name does not" - + " match package name."); + "isEnabledCredentialProviderService: Component name does " + + "not match package name."); return false; } MetricUtilities.logApiCalledSimpleV1( @@ -813,7 +809,6 @@ public final class CredentialManagerService @Override public List<CredentialProviderInfo> getCredentialProviderServices( int userId, int providerFilter) { - Log.i(TAG, "getCredentialProviderServices"); verifyGetProvidersPermission(); return CredentialProviderInfoFactory.getCredentialProviderServices( @@ -823,7 +818,6 @@ public final class CredentialManagerService @Override public List<CredentialProviderInfo> getCredentialProviderServicesForTesting( int providerFilter) { - Log.i(TAG, "getCredentialProviderServicesForTesting"); verifyGetProvidersPermission(); final int userId = UserHandle.getCallingUserId(); @@ -844,8 +838,8 @@ public final class CredentialManagerService .getServiceInfo().getComponentName()); } catch (NullPointerException e) { // Safe check - Log.i(TAG, "Skipping provider as either the providerInfo" - + "or serviceInfo is null - weird"); + Slog.e(TAG, "Skipping provider as either the providerInfo" + + " or serviceInfo is null - weird"); } }); } @@ -858,7 +852,8 @@ public final class CredentialManagerService IClearCredentialStateCallback callback, String callingPackage) { final long timestampBegan = System.nanoTime(); - Log.i(TAG, "starting clearCredentialState with callingPackage: " + callingPackage); + Slog.d(TAG, "starting clearCredentialState with callingPackage: " + + callingPackage); final int userId = UserHandle.getCallingUserId(); int callingUid = Binder.getCallingUid(); enforceCallingPackage(callingPackage, callingUid); @@ -885,13 +880,13 @@ public final class CredentialManagerService if (providerSessions.isEmpty()) { try { // TODO("Replace with properly defined error type") - callback.onError("UNKNOWN", "No crdentials available on this " + "device"); + callback.onError("UNKNOWN", "No credentials available on " + + "this device"); } catch (RemoteException e) { - Log.i( + Slog.e( TAG, "Issue invoking onError on IClearCredentialStateCallback " - + "callback: " - + e.getMessage()); + + "callback: ", e); } } @@ -906,7 +901,7 @@ public final class CredentialManagerService public void registerCredentialDescription( RegisterCredentialDescriptionRequest request, String callingPackage) throws IllegalArgumentException, NonCredentialProviderCallerException { - Log.i(TAG, "registerCredentialDescription"); + Slog.d(TAG, "registerCredentialDescription with callingPackage: " + callingPackage); if (!isCredentialDescriptionApiEnabled()) { throw new UnsupportedOperationException(); @@ -924,7 +919,9 @@ public final class CredentialManagerService public void unregisterCredentialDescription( UnregisterCredentialDescriptionRequest request, String callingPackage) throws IllegalArgumentException { - Log.i(TAG, "registerCredentialDescription"); + Slog.d(TAG, "unregisterCredentialDescription with callingPackage: " + + callingPackage); + if (!isCredentialDescriptionApiEnabled()) { throw new UnsupportedOperationException(); diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java index ee55a1ccc357..91be2a734e85 100644 --- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java +++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java @@ -23,7 +23,6 @@ import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; import android.credentials.CredentialProviderInfo; import android.service.credentials.CredentialProviderInfoFactory; -import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; @@ -41,14 +40,15 @@ public final class CredentialManagerServiceImpl extends // TODO(b/210531) : Make final when update flow is fixed @GuardedBy("mLock") - @NonNull private CredentialProviderInfo mInfo; + @NonNull + private CredentialProviderInfo mInfo; CredentialManagerServiceImpl( @NonNull CredentialManagerService master, @NonNull Object lock, int userId, String serviceName) throws PackageManager.NameNotFoundException { super(master, lock, userId); - Log.i(TAG, "in CredentialManagerServiceImpl constructed with: " + serviceName); + Slog.d(TAG, "CredentialManagerServiceImpl constructed for: " + serviceName); synchronized (mLock) { newServiceInfoLocked(ComponentName.unflattenFromString(serviceName)); } @@ -63,10 +63,8 @@ public final class CredentialManagerServiceImpl extends @NonNull CredentialManagerService master, @NonNull Object lock, int userId, CredentialProviderInfo providerInfo) { super(master, lock, userId); - Log.i(TAG, "in CredentialManagerServiceImpl constructed with system constructor: " - + providerInfo.isSystemProvider() - + " , " + providerInfo.getServiceInfo() == null ? "" : - providerInfo.getServiceInfo().getComponentName().flattenToString()); + Slog.d(TAG, "CredentialManagerServiceImpl constructed for: " + + providerInfo.getServiceInfo().getComponentName().flattenToString()); mInfo = providerInfo; } @@ -76,12 +74,12 @@ public final class CredentialManagerServiceImpl extends throws PackageManager.NameNotFoundException { // TODO : Test update flows with multiple providers if (mInfo != null) { - Log.i(TAG, "newServiceInfoLocked with : " + Slog.d(TAG, "newServiceInfoLocked, mInfo not null : " + mInfo.getServiceInfo().getComponentName().flattenToString() + " , " - + serviceComponent.getPackageName()); + + serviceComponent.flattenToString()); } else { - Log.i(TAG, "newServiceInfoLocked with null mInfo , " - + serviceComponent.getPackageName()); + Slog.d(TAG, "newServiceInfoLocked, mInfo null, " + + serviceComponent.flattenToString()); } mInfo = CredentialProviderInfoFactory.create( getContext(), serviceComponent, @@ -90,18 +88,18 @@ public final class CredentialManagerServiceImpl extends } /** - * Starts a provider session and associates it with the given request session. */ + * Starts a provider session and associates it with the given request session. + */ @Nullable @GuardedBy("mLock") public ProviderSession initiateProviderSessionForRequestLocked( RequestSession requestSession, List<String> requestOptions) { if (!requestOptions.isEmpty() && !isServiceCapableLocked(requestOptions)) { - Log.i(TAG, "Service is not capable"); + Slog.d(TAG, "Service does not have the required capabilities"); return null; } - Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl"); if (mInfo == null) { - Slog.i(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl, " + Slog.w(TAG, "in initiateProviderSessionForRequest in CredManServiceImpl, " + "but mInfo is null. This shouldn't happen"); return null; } @@ -114,15 +112,11 @@ public final class CredentialManagerServiceImpl extends @GuardedBy("mLock") boolean isServiceCapableLocked(List<String> requestedOptions) { if (mInfo == null) { - Slog.i(TAG, "in isServiceCapable, mInfo is null"); return false; } for (String capability : requestedOptions) { if (mInfo.hasCapability(capability)) { - Slog.i(TAG, "Provider can handle: " + capability); return true; - } else { - Slog.i(TAG, "Provider cannot handle: " + capability); } } return false; @@ -146,7 +140,7 @@ public final class CredentialManagerServiceImpl extends try { newServiceInfoLocked(mInfo.getServiceInfo().getComponentName()); } catch (PackageManager.NameNotFoundException e) { - Log.i(TAG, "Issue while updating serviceInfo: " + e.getMessage()); + Slog.e(TAG, "Issue while updating serviceInfo: " + e.getMessage()); } } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 2479646e4561..b5a5d94486da 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -10617,38 +10617,59 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * - SYSTEM_UID * - adb unless hasIncompatibleAccountsOrNonAdb is true. */ + @GuardedBy("getLockObject()") private void enforceCanSetProfileOwnerLocked( - CallerIdentity caller, @Nullable ComponentName owner, int userHandle, + CallerIdentity caller, @Nullable ComponentName owner, @UserIdInt int userId, boolean hasIncompatibleAccountsOrNonAdb) { - UserInfo info = getUserInfo(userHandle); + UserInfo info = getUserInfo(userId); if (info == null) { // User doesn't exist. throw new IllegalArgumentException( - "Attempted to set profile owner for invalid userId: " + userHandle); + "Attempted to set profile owner for invalid userId: " + userId); } if (info.isGuest()) { throw new IllegalStateException("Cannot set a profile owner on a guest"); } - if (mOwners.hasProfileOwner(userHandle)) { - throw new IllegalStateException("Trying to set the profile owner, but profile owner " - + "is already set."); + if (mOwners.hasProfileOwner(userId)) { + StringBuilder errorMessage = new StringBuilder("Trying to set the profile owner"); + if (!hasIncompatibleAccountsOrNonAdb) { + append(errorMessage, owner).append(" on user ").append(userId); + } + errorMessage.append(", but profile owner"); + if (!hasIncompatibleAccountsOrNonAdb) { + appendProfileOwnerLocked(errorMessage, userId); + } + + throw new IllegalStateException(errorMessage.append(" is already set.").toString()); } - if (mOwners.hasDeviceOwner() && mOwners.getDeviceOwnerUserId() == userHandle) { - throw new IllegalStateException("Trying to set the profile owner, but the user " - + "already has a device owner."); + if (mOwners.hasDeviceOwner() && mOwners.getDeviceOwnerUserId() == userId) { + StringBuilder errorMessage = new StringBuilder("Trying to set the profile owner"); + if (!hasIncompatibleAccountsOrNonAdb) { + append(errorMessage, owner).append(" on user ").append(userId); + } + errorMessage.append(", but the user already has a device owner"); + if (!hasIncompatibleAccountsOrNonAdb) { + appendDeviceOwnerLocked(errorMessage); + } + throw new IllegalStateException(errorMessage.append('.').toString()); } if (isAdb(caller)) { - if ((mIsWatch || hasUserSetupCompleted(userHandle)) + if ((mIsWatch || hasUserSetupCompleted(userId)) && hasIncompatibleAccountsOrNonAdb) { - throw new IllegalStateException("Not allowed to set the profile owner because " - + "there are already some accounts on the profile"); + StringBuilder errorMessage = new StringBuilder("Not allowed to set the profile " + + "owner"); + if (!hasIncompatibleAccountsOrNonAdb) { + append(errorMessage, owner).append(" on user ").append(userId).append(' '); + } + throw new IllegalStateException(errorMessage.append(" because there are already " + + "some accounts on the profile.").toString()); } return; } Preconditions.checkCallAuthorization( hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); - if ((mIsWatch || hasUserSetupCompleted(userHandle))) { + if ((mIsWatch || hasUserSetupCompleted(userId))) { Preconditions.checkState(isSystemUid(caller), "Cannot set the profile owner on a user which is already set-up"); @@ -10665,31 +10686,62 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * The Device owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS * permission. */ + @GuardedBy("getLockObject()") private void enforceCanSetDeviceOwnerLocked( CallerIdentity caller, @Nullable ComponentName owner, @UserIdInt int deviceOwnerUserId, boolean hasIncompatibleAccountsOrNonAdb) { + boolean showComponentOnError = false; if (!isAdb(caller)) { Preconditions.checkCallAuthorization( hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); + } else { + showComponentOnError = true; } final int code = checkDeviceOwnerProvisioningPreConditionLocked(owner, /* deviceOwnerUserId= */ deviceOwnerUserId, /* callingUserId*/ caller.getUserId(), isAdb(caller), hasIncompatibleAccountsOrNonAdb); if (code != STATUS_OK) { - throw new IllegalStateException( - computeProvisioningErrorString(code, deviceOwnerUserId)); + throw new IllegalStateException(computeProvisioningErrorStringLocked(code, + deviceOwnerUserId, owner, showComponentOnError)); + } + } + + private String computeProvisioningErrorString(int code, @UserIdInt int userId) { + synchronized (getLockObject()) { + return computeProvisioningErrorStringLocked(code, userId, /* newOwner= */ null, + /* showComponentOnError= */ false); } } - private static String computeProvisioningErrorString(int code, @UserIdInt int userId) { + @GuardedBy("getLockObject()") + private String computeProvisioningErrorStringLocked(int code, @UserIdInt int userId, + @Nullable ComponentName newOwner, boolean showComponentOnError) { switch (code) { case STATUS_OK: return "OK"; - case STATUS_HAS_DEVICE_OWNER: - return "Trying to set the device owner, but device owner is already set."; - case STATUS_USER_HAS_PROFILE_OWNER: - return "Trying to set the device owner, but the user already has a profile owner."; + case STATUS_HAS_DEVICE_OWNER: { + StringBuilder error = new StringBuilder("Trying to set the device owner"); + if (showComponentOnError && newOwner != null) { + append(error, newOwner); + } + error.append(", but device owner"); + if (showComponentOnError) { + appendDeviceOwnerLocked(error); + } + return error.append(" is already set.").toString(); + } + case STATUS_USER_HAS_PROFILE_OWNER: { + StringBuilder error = new StringBuilder("Trying to set the device owner"); + if (showComponentOnError && newOwner != null) { + append(error, newOwner); + } + error.append(", but the user already has a profile owner"); + if (showComponentOnError) { + appendProfileOwnerLocked(error, userId); + } + return error.append(".").toString(); + } case STATUS_USER_NOT_RUNNING: return "User " + userId + " not running."; case STATUS_NOT_SYSTEM_USER: @@ -10708,7 +10760,32 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { default: return "Unexpected @ProvisioningPreCondition: " + code; } + } + @GuardedBy("getLockObject()") + private void appendDeviceOwnerLocked(StringBuilder string) { + ComponentName deviceOwner = getDeviceOwnerComponent(/* callingUserOnly= */ false); + if (deviceOwner == null) { + // Shouldn't happen, but it doesn't hurt to check... + Slogf.wtf(LOG_TAG, "appendDeviceOwnerLocked(): device has no DO set"); + return; + } + append(string, deviceOwner); + } + + @GuardedBy("getLockObject()") + private void appendProfileOwnerLocked(StringBuilder string, @UserIdInt int userId) { + ComponentName profileOwner = mOwners.getProfileOwnerComponent(userId); + if (profileOwner == null) { + // Shouldn't happen, but it doesn't hurt to check... + Slogf.wtf(LOG_TAG, "profileOwner(%d): PO not set", userId); + return; + } + append(string, profileOwner); + } + + private static StringBuilder append(StringBuilder string, ComponentName component) { + return string.append(" (").append(component.flattenToShortString()).append(')'); } private void enforceUserUnlocked(int userId) { @@ -19654,7 +19731,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public void setApplicationExemptions(String packageName, int[] exemptions) { + public void setApplicationExemptions(String callerPackage, String packageName, + int[] exemptions) { if (!mHasFeature) { return; } @@ -19665,7 +19743,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization( hasCallingOrSelfPermission(permission.MANAGE_DEVICE_POLICY_APP_EXEMPTIONS)); - final CallerIdentity caller = getCallerIdentity(); + final CallerIdentity caller = getCallerIdentity(callerPackage); final ApplicationInfo packageInfo; packageInfo = getPackageInfoWithNullCheck(packageName, caller); diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index 8994a488bd56..ab8f3f2279fe 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -383,6 +383,7 @@ public class UserControllerTest { // Call dispatchUserSwitch and verify that observer was called only once mInjector.mHandler.clearAllRecordedMessages(); mUserController.dispatchUserSwitch(userState, oldUserId, newUserId); + verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any()); Set<Integer> expectedCodes = Collections.singleton(CONTINUE_USER_SWITCH_MSG); Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes(); @@ -413,6 +414,7 @@ public class UserControllerTest { // Call dispatchUserSwitch and verify that observer was called only once mInjector.mHandler.clearAllRecordedMessages(); mUserController.dispatchUserSwitch(userState, oldUserId, newUserId); + verify(observer, times(1)).onBeforeUserSwitching(eq(TEST_USER_ID)); verify(observer, times(1)).onUserSwitching(eq(TEST_USER_ID), any()); // Verify that CONTINUE_USER_SWITCH_MSG is not sent (triggers timeout) Set<Integer> actualCodes = mInjector.mHandler.getMessageCodes(); diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml index e8e3a8f84f21..09ee59816a2c 100644 --- a/services/tests/uiservicestests/AndroidManifest.xml +++ b/services/tests/uiservicestests/AndroidManifest.xml @@ -32,6 +32,7 @@ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.OBSERVE_ROLE_HOLDERS" /> <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT"/> + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.WRITE_DEVICE_CONFIG" /> <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> diff --git a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java index 182bf949af1f..82bc6f6c5263 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java +++ b/services/tests/uiservicestests/src/com/android/server/UiServiceTestCase.java @@ -16,8 +16,10 @@ package com.android.server; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.content.Intent; import android.content.pm.PackageManagerInternal; import android.net.Uri; import android.os.Build; @@ -44,8 +46,8 @@ public class UiServiceTestCase { protected static final String PKG_R = "com.example.r"; @Rule - public final TestableContext mContext = - new TestableContext(InstrumentationRegistry.getContext(), null); + public TestableContext mContext = + spy(new TestableContext(InstrumentationRegistry.getContext(), null)); protected TestableContext getContext() { return mContext; @@ -81,6 +83,11 @@ public class UiServiceTestCase { LocalServices.addService(UriGrantsManagerInternal.class, mUgmInternal); when(mUgmInternal.checkGrantUriPermission( anyInt(), anyString(), any(Uri.class), anyInt(), anyInt())).thenReturn(-1); + + Mockito.doReturn(new Intent()).when(mContext).registerReceiverAsUser( + any(), any(), any(), any(), any()); + Mockito.doReturn(new Intent()).when(mContext).registerReceiver(any(), any()); + Mockito.doNothing().when(mContext).unregisterReceiver(any()); } @After diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java index ce076217f37b..8fcbf2f9e97a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java @@ -130,13 +130,18 @@ public class ManagedServicesTest extends UiServiceTestCase { private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedPrimary; private ArrayMap<Integer, ArrayMap<Integer, String>> mExpectedSecondary; + private UserHandle mUser; + private String mPkg; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - getContext().setMockPackageManager(mPm); - getContext().addMockSystemService(Context.USER_SERVICE, mUm); - getContext().addMockSystemService(DEVICE_POLICY_SERVICE, mDpm); + mContext.setMockPackageManager(mPm); + mContext.addMockSystemService(Context.USER_SERVICE, mUm); + mContext.addMockSystemService(DEVICE_POLICY_SERVICE, mDpm); + mUser = mContext.getUser(); + mPkg = mContext.getPackageName(); List<UserInfo> users = new ArrayList<>(); users.add(mZero); @@ -861,8 +866,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -891,8 +896,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -921,8 +926,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -951,8 +956,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -981,8 +986,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -1011,8 +1016,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -1437,8 +1442,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -1464,8 +1469,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -1492,8 +1497,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -1522,8 +1527,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -1552,8 +1557,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -1791,8 +1796,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -1837,8 +1842,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); @@ -1880,8 +1885,8 @@ public class ManagedServicesTest extends UiServiceTestCase { ApplicationInfo ai = new ApplicationInfo(); ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; - when(context.getPackageName()).thenReturn(mContext.getPackageName()); - when(context.getUserId()).thenReturn(mContext.getUserId()); + when(context.getPackageName()).thenReturn(mPkg); + when(context.getUserId()).thenReturn(mUser.getIdentifier()); when(context.getPackageManager()).thenReturn(pm); when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java index d73a3b8e44a6..95fae0707304 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationComparatorTest.java @@ -33,9 +33,11 @@ import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Person; +import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.graphics.Color; import android.os.Build; import android.os.UserHandle; @@ -63,7 +65,7 @@ import java.util.List; @SmallTest @RunWith(Parameterized.class) public class NotificationComparatorTest extends UiServiceTestCase { - @Mock Context mContext; + @Mock Context mMockContext; @Mock TelecomManager mTm; @Mock RankingHandler handler; @Mock PackageManager mPm; @@ -115,32 +117,35 @@ public class NotificationComparatorTest extends UiServiceTestCase { int userId = UserHandle.myUserId(); - when(mContext.getResources()).thenReturn(getContext().getResources()); - when(mContext.getTheme()).thenReturn(getContext().getTheme()); - when(mContext.getContentResolver()).thenReturn(getContext().getContentResolver()); - when(mContext.getPackageManager()).thenReturn(mPm); - when(mContext.getSystemService(eq(Context.TELECOM_SERVICE))).thenReturn(mTm); - when(mContext.getSystemService(Vibrator.class)).thenReturn(mVibrator); - when(mContext.getString(anyInt())).thenCallRealMethod(); - when(mContext.getColor(anyInt())).thenCallRealMethod(); + final Resources res = mContext.getResources(); + when(mMockContext.getResources()).thenReturn(res); + final Resources.Theme theme = mContext.getTheme(); + when(mMockContext.getTheme()).thenReturn(theme); + final ContentResolver cr = mContext.getContentResolver(); + when(mMockContext.getContentResolver()).thenReturn(cr); + when(mMockContext.getPackageManager()).thenReturn(mPm); + when(mMockContext.getSystemService(eq(mMockContext.TELECOM_SERVICE))).thenReturn(mTm); + when(mMockContext.getSystemService(Vibrator.class)).thenReturn(mVibrator); + when(mMockContext.getString(anyInt())).thenCallRealMethod(); + when(mMockContext.getColor(anyInt())).thenCallRealMethod(); when(mTm.getDefaultDialerPackage()).thenReturn(callPkg); final ApplicationInfo legacy = new ApplicationInfo(); legacy.targetSdkVersion = Build.VERSION_CODES.N_MR1; try { when(mPm.getApplicationInfoAsUser(anyString(), anyInt(), anyInt())).thenReturn(legacy); - when(mContext.getApplicationInfo()).thenReturn(legacy); + when(mMockContext.getApplicationInfo()).thenReturn(legacy); } catch (PackageManager.NameNotFoundException e) { // let's hope not } - smsPkg = Settings.Secure.getString(mContext.getContentResolver(), + smsPkg = Settings.Secure.getString(mMockContext.getContentResolver(), Settings.Secure.SMS_DEFAULT_APPLICATION); - Notification nonInterruptiveNotif = new Notification.Builder(mContext, TEST_CHANNEL_ID) + Notification nonInterruptiveNotif = new Notification.Builder(mMockContext, TEST_CHANNEL_ID) .setCategory(Notification.CATEGORY_CALL) .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) .build(); - mRecordMinCallNonInterruptive = new NotificationRecord(mContext, + mRecordMinCallNonInterruptive = new NotificationRecord(mMockContext, new StatusBarNotification(callPkg, callPkg, 1, "mRecordMinCallNonInterruptive", callUid, callUid, nonInterruptiveNotif, @@ -148,134 +153,134 @@ public class NotificationComparatorTest extends UiServiceTestCase { mRecordMinCallNonInterruptive.setSystemImportance(NotificationManager.IMPORTANCE_MIN); mRecordMinCallNonInterruptive.setInterruptive(false); - Notification n1 = new Notification.Builder(mContext, TEST_CHANNEL_ID) + Notification n1 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID) .setCategory(Notification.CATEGORY_CALL) .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) .build(); - mRecordMinCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg, + mRecordMinCall = new NotificationRecord(mMockContext, new StatusBarNotification(callPkg, callPkg, 1, "minCall", callUid, callUid, n1, new UserHandle(userId), "", 2000), getDefaultChannel()); mRecordMinCall.setSystemImportance(NotificationManager.IMPORTANCE_MIN); mRecordMinCall.setInterruptive(true); - Notification n2 = new Notification.Builder(mContext, TEST_CHANNEL_ID) + Notification n2 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID) .setCategory(Notification.CATEGORY_CALL) .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) .build(); - mRecordHighCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg, + mRecordHighCall = new NotificationRecord(mMockContext, new StatusBarNotification(callPkg, callPkg, 1, "highcall", callUid, callUid, n2, new UserHandle(userId), "", 1999), getDefaultChannel()); mRecordHighCall.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); - Notification nHighCallStyle = new Notification.Builder(mContext, TEST_CHANNEL_ID) + Notification nHighCallStyle = new Notification.Builder(mMockContext, TEST_CHANNEL_ID) .setStyle(Notification.CallStyle.forOngoingCall( new Person.Builder().setName("caller").build(), mock(PendingIntent.class) )) .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) .build(); - mRecordHighCallStyle = new NotificationRecord(mContext, new StatusBarNotification(callPkg, - callPkg, 1, "highCallStyle", callUid, callUid, nHighCallStyle, + mRecordHighCallStyle = new NotificationRecord(mMockContext, new StatusBarNotification( + callPkg, callPkg, 1, "highCallStyle", callUid, callUid, nHighCallStyle, new UserHandle(userId), "", 2000), getDefaultChannel()); mRecordHighCallStyle.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); mRecordHighCallStyle.setInterruptive(true); - Notification n4 = new Notification.Builder(mContext, TEST_CHANNEL_ID) + Notification n4 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID) .setStyle(new Notification.MessagingStyle("sender!")).build(); - mRecordInlineReply = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + mRecordInlineReply = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2, pkg2, 1, "inlinereply", uid2, uid2, n4, new UserHandle(userId), "", 1599), getDefaultChannel()); mRecordInlineReply.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); mRecordInlineReply.setPackagePriority(Notification.PRIORITY_MAX); if (smsPkg != null) { - Notification n5 = new Notification.Builder(mContext, TEST_CHANNEL_ID) + Notification n5 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID) .setCategory(Notification.CATEGORY_MESSAGE).build(); - mRecordSms = new NotificationRecord(mContext, new StatusBarNotification(smsPkg, + mRecordSms = new NotificationRecord(mMockContext, new StatusBarNotification(smsPkg, smsPkg, 1, "sms", smsUid, smsUid, n5, new UserHandle(userId), "", 1299), getDefaultChannel()); mRecordSms.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT); } - Notification n6 = new Notification.Builder(mContext, TEST_CHANNEL_ID).build(); - mRecordStarredContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + Notification n6 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID).build(); + mRecordStarredContact = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2, pkg2, 1, "starred", uid2, uid2, n6, new UserHandle(userId), "", 1259), getDefaultChannel()); mRecordStarredContact.setContactAffinity(ValidateNotificationPeople.STARRED_CONTACT); mRecordStarredContact.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT); - Notification n7 = new Notification.Builder(mContext, TEST_CHANNEL_ID).build(); - mRecordContact = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + Notification n7 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID).build(); + mRecordContact = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2, pkg2, 1, "contact", uid2, uid2, n7, new UserHandle(userId), "", 1259), getDefaultChannel()); mRecordContact.setContactAffinity(ValidateNotificationPeople.VALID_CONTACT); mRecordContact.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT); - Notification nSystemMax = new Notification.Builder(mContext, TEST_CHANNEL_ID).build(); - mRecordSystemMax = new NotificationRecord(mContext, new StatusBarNotification(sysPkg, + Notification nSystemMax = new Notification.Builder(mMockContext, TEST_CHANNEL_ID).build(); + mRecordSystemMax = new NotificationRecord(mMockContext, new StatusBarNotification(sysPkg, sysPkg, 1, "systemmax", uid2, uid2, nSystemMax, new UserHandle(userId), "", 1244), getDefaultChannel()); mRecordSystemMax.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); - Notification n8 = new Notification.Builder(mContext, TEST_CHANNEL_ID).build(); - mRecordUrgent = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + Notification n8 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID).build(); + mRecordUrgent = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2, pkg2, 1, "urgent", uid2, uid2, n8, new UserHandle(userId), "", 1258), getDefaultChannel()); mRecordUrgent.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); - Notification n9 = new Notification.Builder(mContext, TEST_CHANNEL_ID) + Notification n9 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID) .setCategory(Notification.CATEGORY_MESSAGE) .setFlag(Notification.FLAG_ONGOING_EVENT |Notification.FLAG_FOREGROUND_SERVICE, true) .build(); - mRecordCheater = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + mRecordCheater = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2, pkg2, 1, "cheater", uid2, uid2, n9, new UserHandle(userId), "", 9258), getDefaultChannel()); mRecordCheater.setSystemImportance(NotificationManager.IMPORTANCE_LOW); mRecordCheater.setPackagePriority(Notification.PRIORITY_MAX); - Notification n10 = new Notification.Builder(mContext, TEST_CHANNEL_ID) + Notification n10 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID) .setStyle(new Notification.InboxStyle().setSummaryText("message!")).build(); - mRecordEmail = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + mRecordEmail = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2, pkg2, 1, "email", uid2, uid2, n10, new UserHandle(userId), "", 1599), getDefaultChannel()); mRecordEmail.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); - Notification n11 = new Notification.Builder(mContext, TEST_CHANNEL_ID) + Notification n11 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID) .setCategory(Notification.CATEGORY_MESSAGE) .setColorized(true).setColor(Color.WHITE) .build(); - mRecordCheaterColorized = new NotificationRecord(mContext, new StatusBarNotification(pkg2, - pkg2, 1, "cheaterColorized", uid2, uid2, n11, new UserHandle(userId), - "", 9258), getDefaultChannel()); + mRecordCheaterColorized = new NotificationRecord(mMockContext, + new StatusBarNotification(pkg2,pkg2, 1, "cheaterColorized", uid2, uid2, n11, + new UserHandle(userId), "", 9258), getDefaultChannel()); mRecordCheaterColorized.setSystemImportance(NotificationManager.IMPORTANCE_LOW); - Notification n12 = new Notification.Builder(mContext, TEST_CHANNEL_ID) + Notification n12 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID) .setCategory(Notification.CATEGORY_MESSAGE) .setColorized(true).setColor(Color.WHITE) .setStyle(new Notification.MediaStyle()) .build(); - mNoMediaSessionMedia = new NotificationRecord(mContext, new StatusBarNotification( + mNoMediaSessionMedia = new NotificationRecord(mMockContext, new StatusBarNotification( pkg2, pkg2, 1, "media", uid2, uid2, n12, new UserHandle(userId), "", 9258), getDefaultChannel()); mNoMediaSessionMedia.setSystemImportance(NotificationManager.IMPORTANCE_DEFAULT); - Notification n13 = new Notification.Builder(mContext, TEST_CHANNEL_ID) + Notification n13 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID) .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) .setColorized(true).setColor(Color.WHITE) .build(); - mRecordColorized = new NotificationRecord(mContext, new StatusBarNotification(pkg2, + mRecordColorized = new NotificationRecord(mMockContext, new StatusBarNotification(pkg2, pkg2, 1, "colorized", uid2, uid2, n13, new UserHandle(userId), "", 1999), getDefaultChannel()); mRecordColorized.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); - Notification n14 = new Notification.Builder(mContext, TEST_CHANNEL_ID) + Notification n14 = new Notification.Builder(mMockContext, TEST_CHANNEL_ID) .setCategory(Notification.CATEGORY_CALL) .setColorized(true).setColor(Color.WHITE) .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true) .build(); - mRecordColorizedCall = new NotificationRecord(mContext, new StatusBarNotification(callPkg, - callPkg, 1, "colorizedCall", callUid, callUid, n14, + mRecordColorizedCall = new NotificationRecord(mMockContext, new StatusBarNotification( + callPkg, callPkg, 1, "colorizedCall", callUid, callUid, n14, new UserHandle(userId), "", 1999), getDefaultChannel()); mRecordColorizedCall.setSystemImportance(NotificationManager.IMPORTANCE_HIGH); } @@ -316,14 +321,14 @@ public class NotificationComparatorTest extends UiServiceTestCase { actual.addAll(expected); Collections.shuffle(actual); - Collections.sort(actual, new NotificationComparator(mContext)); + Collections.sort(actual, new NotificationComparator(mMockContext)); assertThat(actual).containsExactlyElementsIn(expected).inOrder(); } @Test public void testRankingScoreOverrides() { - NotificationComparator comp = new NotificationComparator(mContext); + NotificationComparator comp = new NotificationComparator(mMockContext); NotificationRecord recordMinCallNonInterruptive = spy(mRecordMinCallNonInterruptive); if (mSortByInterruptiveness) { assertTrue(comp.compare(mRecordMinCall, recordMinCallNonInterruptive) < 0); @@ -339,7 +344,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { @Test public void testMessaging() { - NotificationComparator comp = new NotificationComparator(mContext); + NotificationComparator comp = new NotificationComparator(mMockContext); assertTrue(comp.isImportantMessaging(mRecordInlineReply)); if (mRecordSms != null) { assertTrue(comp.isImportantMessaging(mRecordSms)); @@ -350,7 +355,7 @@ public class NotificationComparatorTest extends UiServiceTestCase { @Test public void testPeople() { - NotificationComparator comp = new NotificationComparator(mContext); + NotificationComparator comp = new NotificationComparator(mMockContext); assertTrue(comp.isImportantPeople(mRecordStarredContact)); assertTrue(comp.isImportantPeople(mRecordContact)); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java index 1b42fd3bb241..60f1e66b7e94 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java @@ -30,6 +30,7 @@ import android.app.NotificationHistory.HistoricalNotification; import android.content.Context; import android.graphics.drawable.Icon; import android.os.Handler; +import android.os.UserHandle; import android.util.AtomicFile; import androidx.test.InstrumentationRegistry; @@ -56,8 +57,6 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { File mRootDir; @Mock Handler mFileWriteHandler; - @Mock - Context mContext; NotificationHistoryDatabase mDataBase; @@ -92,10 +91,8 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - when(mContext.getUser()).thenReturn(getContext().getUser()); - when(mContext.getPackageName()).thenReturn(getContext().getPackageName()); - - mRootDir = new File(mContext.getFilesDir(), "NotificationHistoryDatabaseTest"); + final File fileDir = mContext.getFilesDir(); + mRootDir = new File(fileDir, "NotificationHistoryDatabaseTest"); mDataBase = new NotificationHistoryDatabase(mFileWriteHandler, mRootDir); mDataBase.init(); 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 9ca8d8444df9..42d1ace37ba5 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -307,7 +307,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { @Mock private PermissionHelper mPermissionHelper; private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake(); - private TestableContext mContext = spy(getContext()); private final String PKG = mContext.getPackageName(); private TestableLooper mTestableLooper; @Mock @@ -425,7 +424,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // Shell permisssions will override permissions of our app, so add all necessary permissions // for this test here: InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity( - "android.permission.ALLOWLISTED_WRITE_DEVICE_CONFIG", + "android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG", "android.permission.READ_DEVICE_CONFIG", "android.permission.READ_CONTACTS"); @@ -578,9 +577,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor.forClass(IntentFilter.class); - Mockito.doReturn(new Intent()).when(mContext).registerReceiverAsUser( - any(), any(), any(), any(), any()); - Mockito.doReturn(new Intent()).when(mContext).registerReceiver(any(), any()); verify(mContext, atLeastOnce()).registerReceiverAsUser(broadcastReceiverCaptor.capture(), any(), intentFilterCaptor.capture(), any(), any()); verify(mContext, atLeastOnce()).registerReceiver(broadcastReceiverCaptor.capture(), @@ -611,6 +607,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(), anyString(), anyInt(), any())).thenReturn(true); when(mUserManager.isUserUnlocked(any(UserHandle.class))).thenReturn(true); + mockIsUserVisible(DEFAULT_DISPLAY, true); mockIsVisibleBackgroundUsersSupported(false); // Set the testable bubble extractor @@ -6913,6 +6910,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testTextToastsCallStatusBar_nonUiContext_secondaryDisplay() throws Exception { allowTestPackageToToast(); + mockIsUserVisible(SECONDARY_DISPLAY_ID, true); enqueueTextToast(TEST_PACKAGE, "Text", /* isUiContext= */ false, SECONDARY_DISPLAY_ID); @@ -6936,6 +6934,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testTextToastsCallStatusBar_visibleBgUsers_uiContext_secondaryDisplay() throws Exception { mockIsVisibleBackgroundUsersSupported(true); + mockIsUserVisible(SECONDARY_DISPLAY_ID, true); mockDisplayAssignedToUser(INVALID_DISPLAY); // make sure it's not used allowTestPackageToToast(); @@ -6960,6 +6959,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { public void testTextToastsCallStatusBar_visibleBgUsers_nonUiContext_secondaryDisplay() throws Exception { mockIsVisibleBackgroundUsersSupported(true); + mockIsUserVisible(SECONDARY_DISPLAY_ID, true); mockDisplayAssignedToUser(INVALID_DISPLAY); // make sure it's not used allowTestPackageToToast(); @@ -6969,6 +6969,26 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test + public void testTextToastsCallStatusBar_userNotVisibleOnDisplay() throws Exception { + final String testPackage = "testPackageName"; + assertEquals(0, mService.mToastQueue.size()); + mService.isSystemUid = false; + setToastRateIsWithinQuota(true); + setIfPackageHasPermissionToAvoidToastRateLimiting(testPackage, false); + mockIsUserVisible(DEFAULT_DISPLAY, false); + + // package is not suspended + when(mPackageManager.isPackageSuspendedForUser(testPackage, UserHandle.getUserId(mUid))) + .thenReturn(false); + + // enqueue toast -> no toasts enqueued + enqueueTextToast(testPackage, "Text"); + verify(mStatusBar, never()).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any(), + anyInt()); + assertEquals(0, mService.mToastQueue.size()); + } + + @Test public void testDisallowToastsFromSuspendedPackages() throws Exception { final String testPackage = "testPackageName"; assertEquals(0, mService.mToastQueue.size()); @@ -6985,6 +7005,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // enqueue toast -> no toasts enqueued enqueueToast(testPackage, new TestableToastCallback()); + verify(mStatusBar, never()).showToast(anyInt(), any(), any(), any(), any(), anyInt(), any(), + anyInt()); assertEquals(0, mService.mToastQueue.size()); } @@ -10808,6 +10830,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { when(mUm.isVisibleBackgroundUsersSupported()).thenReturn(supported); } + private void mockIsUserVisible(int displayId, boolean visible) { + when(mUmInternal.isUserVisible(mUserId, displayId)).thenReturn(visible); + } + private void mockDisplayAssignedToUser(int displayId) { when(mUmInternal.getMainDisplayAssignedToUser(mUserId)).thenReturn(displayId); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java index 25e74bf5dcd2..fae92d9ac738 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordTest.java @@ -55,6 +55,7 @@ import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; +import android.content.res.Resources; import android.graphics.Color; import android.graphics.drawable.Icon; import android.media.AudioAttributes; @@ -126,7 +127,8 @@ public class NotificationRecordTest extends UiServiceTestCase { MockitoAnnotations.initMocks(this); when(mMockContext.getSystemService(eq(Vibrator.class))).thenReturn(mVibrator); - when(mMockContext.getResources()).thenReturn(getContext().getResources()); + final Resources res = mContext.getResources(); + when(mMockContext.getResources()).thenReturn(res); when(mMockContext.getPackageManager()).thenReturn(mPm); when(mMockContext.getContentResolver()).thenReturn(mContentResolver); ApplicationInfo appInfo = new ApplicationInfo(); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java index fcff228fb591..0222bfbf8605 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationShellCmdTest.java @@ -67,7 +67,6 @@ import java.util.List; public class NotificationShellCmdTest extends UiServiceTestCase { private final Binder mBinder = new Binder(); private final ShellCallback mCallback = new ShellCallback(); - private final TestableContext mTestableContext = spy(getContext()); @Mock NotificationManagerService mMockService; @Mock @@ -82,7 +81,7 @@ public class NotificationShellCmdTest extends UiServiceTestCase { mTestableLooper = TestableLooper.get(this); mResultReceiver = new ResultReceiver(new Handler(mTestableLooper.getLooper())); - when(mMockService.getContext()).thenReturn(mTestableContext); + when(mMockService.getContext()).thenReturn(mContext); when(mMockService.getBinderService()).thenReturn(mMockBinderService); } @@ -116,9 +115,10 @@ public class NotificationShellCmdTest extends UiServiceTestCase { Notification captureNotification(String aTag) throws Exception { ArgumentCaptor<Notification> notificationCaptor = ArgumentCaptor.forClass(Notification.class); + final String pkg = getContext().getPackageName(); verify(mMockBinderService).enqueueNotificationWithTag( - eq(getContext().getPackageName()), - eq(getContext().getPackageName()), + eq(pkg), + eq(pkg), eq(aTag), eq(NotificationShellCmd.NOTIFICATION_ID), notificationCaptor.capture(), diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index bf836ae0eba0..6f9798ea7d69 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -153,7 +153,6 @@ public class ZenModeHelperTest extends UiServiceTestCase { private Resources mResources; private TestableLooper mTestableLooper; private ZenModeHelper mZenModeHelperSpy; - private Context mContext; private ContentResolver mContentResolver; @Mock AppOpsManager mAppOps; private WrappedSysUiStatsEvent.WrappedBuilderFactory mStatsEventBuilderFactory; @@ -163,9 +162,9 @@ public class ZenModeHelperTest extends UiServiceTestCase { MockitoAnnotations.initMocks(this); mTestableLooper = TestableLooper.get(this); - mContext = spy(getContext()); mContentResolver = mContext.getContentResolver(); mResources = spy(mContext.getResources()); + String pkg = mContext.getPackageName(); try { when(mResources.getXml(R.xml.default_zen_mode_config)).thenReturn( getDefaultConfigParser()); @@ -190,7 +189,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { when(mPackageManager.getPackageUidAsUser(eq(CUSTOM_PKG_NAME), anyInt())) .thenReturn(CUSTOM_PKG_UID); when(mPackageManager.getPackagesForUid(anyInt())).thenReturn( - new String[] {getContext().getPackageName()}); + new String[] {pkg}); mZenModeHelperSpy.mPm = mPackageManager; } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java index 3eabea67e890..48a39e682340 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java @@ -621,8 +621,13 @@ final class HotwordDetectionConnection { ServiceConnectionFactory(@NonNull Intent intent, boolean bindInstantServiceAllowed, int detectionServiceType) { mIntent = intent; - mBindingFlags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0; mDetectionServiceType = detectionServiceType; + int flags = bindInstantServiceAllowed ? Context.BIND_ALLOW_INSTANT : 0; + if (mVisualQueryDetectionComponentName != null + && mHotwordDetectionComponentName != null) { + flags |= Context.BIND_SHARED_ISOLATED_PROCESS; + } + mBindingFlags = flags; } ServiceConnection createLocked() { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java index 929e033315f7..62be2a555bc4 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java @@ -738,6 +738,13 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne } else { verifyDetectorForVisualQueryDetectionLocked(sharedMemory); } + if (!verifyProcessSharingLocked()) { + Slog.w(TAG, "Sandboxed detection service not in shared isolated process"); + throw new IllegalStateException("VisualQueryDetectionService or HotworDetectionService " + + "not in a shared isolated process. Please make sure to set " + + "android:allowSharedIsolatedProcess and android:isolatedProcess to be true " + + "and android:externalService to be false in the manifest file"); + } if (mHotwordDetectionConnection == null) { mHotwordDetectionConnection = new HotwordDetectionConnection(mServiceStub, mContext, @@ -931,6 +938,19 @@ class VoiceInteractionManagerServiceImpl implements VoiceInteractionSessionConne return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0 && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0; } + @GuardedBy("this") + boolean verifyProcessSharingLocked() { + // only check this if both VQDS and HDS are declared in the app + ServiceInfo hotwordInfo = getServiceInfoLocked(mHotwordDetectionComponentName, mUser); + ServiceInfo visualQueryInfo = + getServiceInfoLocked(mVisualQueryDetectionComponentName, mUser); + if (hotwordInfo == null || visualQueryInfo == null) { + return true; + } + return (hotwordInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0 + && (visualQueryInfo.flags & ServiceInfo.FLAG_ALLOW_SHARED_ISOLATED_PROCESS) != 0; + } + void forceRestartHotwordDetector() { if (mHotwordDetectionConnection == null) { diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 554beb9a35ba..1ce85ba93d95 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -1186,7 +1186,6 @@ public class ApnSetting implements Parcelable { ApnSetting other = (ApnSetting) o; return mEntryName.equals(other.mEntryName) - && Objects.equals(mId, other.mId) && Objects.equals(mOperatorNumeric, other.mOperatorNumeric) && Objects.equals(mApnName, other.mApnName) && Objects.equals(mProxyAddress, other.mProxyAddress) diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java index 15fd817ba73b..e5ef62b16dfd 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManager.java @@ -38,6 +38,7 @@ import android.os.RemoteException; import android.util.Log; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import java.util.HashMap; import java.util.List; @@ -49,9 +50,14 @@ import java.util.concurrent.Executor; * This class is the library used by consumers of Shared Connectivity data to bind to the service, * receive callbacks from, and send user actions to the service. * + * A client must register at least one callback so that the manager will bind to the service. Once + * all callbacks are unregistered, the manager will unbind from the service. When the client no + * longer needs Shared Connectivity data, the client must unregister. + * * The methods {@link #connectHotspotNetwork}, {@link #disconnectHotspotNetwork}, * {@link #connectKnownNetwork} and {@link #forgetKnownNetwork} are not valid and will return false - * if not called between {@link SharedConnectivityClientCallback#onServiceConnected()} + * and getter methods will fail and return null if not called between + * {@link SharedConnectivityClientCallback#onServiceConnected()} * and {@link SharedConnectivityClientCallback#onServiceDisconnected()} or if * {@link SharedConnectivityClientCallback#onRegisterCallbackFailed} was called. * @@ -139,19 +145,22 @@ public class SharedConnectivityManager { } private ISharedConnectivityService mService; + @GuardedBy("mProxyDataLock") private final Map<SharedConnectivityClientCallback, SharedConnectivityCallbackProxy> mProxyMap = new HashMap<>(); + @GuardedBy("mProxyDataLock") private final Map<SharedConnectivityClientCallback, SharedConnectivityCallbackProxy> mCallbackProxyCache = new HashMap<>(); - // Used for testing - private final ServiceConnection mServiceConnection; + // Makes sure mProxyMap and mCallbackProxyCache are locked together when one of them is used. + private final Object mProxyDataLock = new Object(); + private final Context mContext; + private final String mServicePackageName; + private final String mIntentAction; + private ServiceConnection mServiceConnection; /** * Creates a new instance of {@link SharedConnectivityManager}. * - * Automatically binds to implementation of {@link SharedConnectivityService} specified in - * the device overlay. - * * @return An instance of {@link SharedConnectivityManager} or null if the shared connectivity * service is not found. * @hide @@ -185,12 +194,18 @@ public class SharedConnectivityManager { private SharedConnectivityManager(@NonNull Context context, String servicePackageName, String serviceIntentAction) { + mContext = context; + mServicePackageName = servicePackageName; + mIntentAction = serviceIntentAction; + } + + private void bind() { mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = ISharedConnectivityService.Stub.asInterface(service); - if (!mCallbackProxyCache.isEmpty()) { - synchronized (mCallbackProxyCache) { + synchronized (mProxyDataLock) { + if (!mCallbackProxyCache.isEmpty()) { mCallbackProxyCache.keySet().forEach(callback -> registerCallbackInternal( callback, mCallbackProxyCache.get(callback))); @@ -203,15 +218,13 @@ public class SharedConnectivityManager { public void onServiceDisconnected(ComponentName name) { if (DEBUG) Log.i(TAG, "onServiceDisconnected"); mService = null; - if (!mCallbackProxyCache.isEmpty()) { - synchronized (mCallbackProxyCache) { + synchronized (mProxyDataLock) { + if (!mCallbackProxyCache.isEmpty()) { mCallbackProxyCache.keySet().forEach( SharedConnectivityClientCallback::onServiceDisconnected); mCallbackProxyCache.clear(); } - } - if (!mProxyMap.isEmpty()) { - synchronized (mProxyMap) { + if (!mProxyMap.isEmpty()) { mProxyMap.keySet().forEach( SharedConnectivityClientCallback::onServiceDisconnected); mProxyMap.clear(); @@ -220,8 +233,8 @@ public class SharedConnectivityManager { } }; - context.bindService( - new Intent().setPackage(servicePackageName).setAction(serviceIntentAction), + mContext.bindService( + new Intent().setPackage(mServicePackageName).setAction(mIntentAction), mServiceConnection, Context.BIND_AUTO_CREATE); } @@ -229,7 +242,7 @@ public class SharedConnectivityManager { SharedConnectivityCallbackProxy proxy) { try { mService.registerCallback(proxy); - synchronized (mProxyMap) { + synchronized (mProxyDataLock) { mProxyMap.put(callback, proxy); } callback.onServiceConnected(); @@ -256,10 +269,19 @@ public class SharedConnectivityManager { return mServiceConnection; } + private void unbind() { + if (mServiceConnection != null) { + mContext.unbindService(mServiceConnection); + mServiceConnection = null; + } + } + /** * Registers a callback for receiving updates to the list of Hotspot Networks, Known Networks, * shared connectivity settings state, hotspot network connection status and known network * connection status. + * Automatically binds to implementation of {@link SharedConnectivityService} specified in + * the device overlay when the first callback is registered. * The {@link SharedConnectivityClientCallback#onRegisterCallbackFailed} will be called if the * registration failed. * @@ -284,9 +306,16 @@ public class SharedConnectivityManager { SharedConnectivityCallbackProxy proxy = new SharedConnectivityCallbackProxy(executor, callback); if (mService == null) { - synchronized (mCallbackProxyCache) { + boolean shouldBind; + synchronized (mProxyDataLock) { + // Size can be 1 in different cases of register/unregister sequences. If size is 0 + // Bind never happened or unbind was called. + shouldBind = mCallbackProxyCache.size() == 0; mCallbackProxyCache.put(callback, proxy); } + if (shouldBind) { + bind(); + } return; } registerCallbackInternal(callback, proxy); @@ -294,6 +323,7 @@ public class SharedConnectivityManager { /** * Unregisters a callback. + * Unbinds from {@link SharedConnectivityService} when no more callbacks are registered. * * @return Returns true if the callback was successfully unregistered, false otherwise. */ @@ -309,16 +339,27 @@ public class SharedConnectivityManager { } if (mService == null) { - synchronized (mCallbackProxyCache) { + boolean shouldUnbind; + synchronized (mProxyDataLock) { mCallbackProxyCache.remove(callback); + // Connection was never established, so all registered callbacks are in the cache. + shouldUnbind = mCallbackProxyCache.isEmpty(); + } + if (shouldUnbind) { + unbind(); } return true; } try { - mService.unregisterCallback(mProxyMap.get(callback)); - synchronized (mProxyMap) { + boolean shouldUnbind; + synchronized (mProxyDataLock) { + mService.unregisterCallback(mProxyMap.get(callback)); mProxyMap.remove(callback); + shouldUnbind = mProxyMap.isEmpty(); + } + if (shouldUnbind) { + unbind(); } } catch (RemoteException e) { Log.e(TAG, "Exception in unregisterCallback", e); diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java index 96afe278e3e0..b585bd5cfd7b 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/SharedConnectivityManagerTest.java @@ -28,15 +28,17 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.content.res.Resources; import android.net.wifi.sharedconnectivity.service.ISharedConnectivityService; import android.os.Bundle; -import android.os.Parcel; import android.os.RemoteException; import androidx.test.filters.SmallTest; @@ -80,7 +82,7 @@ public class SharedConnectivityManagerTest { @Mock Executor mExecutor; @Mock - SharedConnectivityClientCallback mClientCallback; + SharedConnectivityClientCallback mClientCallback, mClientCallback2; @Mock Resources mResources; @Mock @@ -95,47 +97,52 @@ public class SharedConnectivityManagerTest { setResources(mContext); } - /** - * Verifies constructor is binding to service. - */ @Test - public void bindingToService() { - SharedConnectivityManager.create(mContext); + public void resourcesNotDefined_createShouldReturnNull() { + when(mResources.getString(anyInt())).thenThrow(new Resources.NotFoundException()); - verify(mContext).bindService(any(), any(), anyInt()); + assertThat(SharedConnectivityManager.create(mContext)).isNull(); } - /** - * Verifies create method returns null when resources are not specified - */ @Test - public void resourcesNotDefined() { - when(mResources.getString(anyInt())).thenThrow(new Resources.NotFoundException()); + public void bindingToServiceOnFirstCallbackRegistration() { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + manager.registerCallback(mExecutor, mClientCallback); - assertThat(SharedConnectivityManager.create(mContext)).isNull(); + verify(mContext).bindService(any(Intent.class), any(ServiceConnection.class), anyInt()); } - /** - * Verifies registerCallback behavior. - */ @Test - public void registerCallback_serviceNotConnected_registrationCachedThenConnected() - throws Exception { + public void bindIsCalledOnceOnMultipleCallbackRegistrations() throws Exception { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); - manager.setService(null); manager.registerCallback(mExecutor, mClientCallback); - manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder); + verify(mContext, times(1)).bindService(any(Intent.class), any(ServiceConnection.class), + anyInt()); + + manager.registerCallback(mExecutor, mClientCallback2); + verify(mContext, times(1)).bindService(any(Intent.class), any(ServiceConnection.class), + anyInt()); + } - // Since the binder is embedded in a proxy class, the call to registerCallback is done on - // the proxy. So instead verifying that the proxy is calling the binder. - verify(mIBinder).transact(anyInt(), any(Parcel.class), any(Parcel.class), anyInt()); + @Test + public void unbindIsCalledOnLastCallbackUnregistrations() throws Exception { + SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); + + manager.registerCallback(mExecutor, mClientCallback); + manager.registerCallback(mExecutor, mClientCallback2); + manager.unregisterCallback(mClientCallback); + verify(mContext, never()).unbindService( + any(ServiceConnection.class)); + + manager.unregisterCallback(mClientCallback2); + verify(mContext, times(1)).unbindService( + any(ServiceConnection.class)); } @Test public void registerCallback_serviceNotConnected_canUnregisterAndReregister() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); - manager.setService(null); manager.registerCallback(mExecutor, mClientCallback); manager.unregisterCallback(mClientCallback); @@ -177,9 +184,6 @@ public class SharedConnectivityManagerTest { verify(mClientCallback).onRegisterCallbackFailed(any(RemoteException.class)); } - /** - * Verifies unregisterCallback behavior. - */ @Test public void unregisterCallback_withoutRegisteringFirst_serviceNotConnected_shouldFail() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); @@ -239,11 +243,8 @@ public class SharedConnectivityManagerTest { assertThat(manager.unregisterCallback(mClientCallback)).isFalse(); } - /** - * Verifies callback is called when service is connected - */ @Test - public void onServiceConnected_registerCallbackBeforeConnection() { + public void onServiceConnected() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.registerCallback(mExecutor, mClientCallback); @@ -253,20 +254,7 @@ public class SharedConnectivityManagerTest { } @Test - public void onServiceConnected_registerCallbackAfterConnection() { - SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); - - manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder); - manager.registerCallback(mExecutor, mClientCallback); - - verify(mClientCallback).onServiceConnected(); - } - - /** - * Verifies callback is called when service is disconnected - */ - @Test - public void onServiceDisconnected_registerCallbackBeforeConnection() { + public void onServiceDisconnected() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); manager.registerCallback(mExecutor, mClientCallback); @@ -276,20 +264,7 @@ public class SharedConnectivityManagerTest { verify(mClientCallback).onServiceDisconnected(); } - @Test - public void onServiceDisconnected_registerCallbackAfterConnection() { - SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); - - manager.getServiceConnection().onServiceConnected(COMPONENT_NAME, mIBinder); - manager.registerCallback(mExecutor, mClientCallback); - manager.getServiceConnection().onServiceDisconnected(COMPONENT_NAME); - - verify(mClientCallback).onServiceDisconnected(); - } - /** - * Verifies connectHotspotNetwork behavior. - */ @Test public void connectHotspotNetwork_serviceNotConnected_shouldFail() { HotspotNetwork network = buildHotspotNetwork(); @@ -320,9 +295,6 @@ public class SharedConnectivityManagerTest { assertThat(manager.connectHotspotNetwork(network)).isFalse(); } - /** - * Verifies disconnectHotspotNetwork behavior. - */ @Test public void disconnectHotspotNetwork_serviceNotConnected_shouldFail() { HotspotNetwork network = buildHotspotNetwork(); @@ -353,9 +325,6 @@ public class SharedConnectivityManagerTest { assertThat(manager.disconnectHotspotNetwork(network)).isFalse(); } - /** - * Verifies connectKnownNetwork behavior. - */ @Test public void connectKnownNetwork_serviceNotConnected_shouldFail() throws RemoteException { KnownNetwork network = buildKnownNetwork(); @@ -386,9 +355,6 @@ public class SharedConnectivityManagerTest { assertThat(manager.connectKnownNetwork(network)).isFalse(); } - /** - * Verifies forgetKnownNetwork behavior. - */ @Test public void forgetKnownNetwork_serviceNotConnected_shouldFail() { KnownNetwork network = buildKnownNetwork(); @@ -419,9 +385,6 @@ public class SharedConnectivityManagerTest { assertThat(manager.forgetKnownNetwork(network)).isFalse(); } - /** - * Verify getters. - */ @Test public void getHotspotNetworks_serviceNotConnected_shouldReturnNull() { SharedConnectivityManager manager = SharedConnectivityManager.create(mContext); |