diff options
72 files changed, 1233 insertions, 545 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 4710322db283..f49cdbf403f0 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -1777,10 +1777,7 @@ public class JobInfo implements Parcelable { * {@link Build.VERSION_CODES#S}, but starting from Android version * {@link Build.VERSION_CODES#TIRAMISU}, expedited jobs for the foreground app are * guaranteed to be started before {@link JobScheduler#schedule(JobInfo)} returns (assuming - * all requested constraints are satisfied), similar to foreground services. However, this - * start guarantee means there is a higher chance of overlapping executions, as noted in - * {@link JobService}, so be sure to handle that properly if you intend to reschedule the - * job while it's actively running. + * all requested constraints are satisfied), similar to foreground services. * * @see JobInfo#isExpedited() */ diff --git a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java index 388bbf1b26a0..632ecb2c0381 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -107,9 +107,7 @@ public abstract class JobScheduler { /** * Schedule a job to be executed. Will replace any currently scheduled job with the same * ID with the new information in the {@link JobInfo}. If a job with the given ID is currently - * running, it will be stopped. Note that in some cases, the newly scheduled job may be started - * before the previously running job has been fully stopped. See {@link JobService} for - * additional details. + * running, it will be stopped. * * <p class="caution"><strong>Note:</strong> Scheduling a job can have a high cost, even if it's * rescheduling the same job and the job didn't execute, especially on platform versions before diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java index 7ed4b62ae7e4..d184d44239ed 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobService.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java @@ -32,16 +32,11 @@ import android.os.IBinder; * {@link #onStopJob(android.app.job.JobParameters)}, which is meant to inform you that the * scheduling requirements are no longer being met.</p> * - * As a subclass of {@link Service}, there will only be one active instance of any JobService + * <p>As a subclass of {@link Service}, there will only be one active instance of any JobService * subclasses, regardless of job ID. This means that if you schedule multiple jobs with different * job IDs but using the same JobService class, that JobService may receive multiple calls to * {@link #onStartJob(JobParameters)} and {@link #onStopJob(JobParameters)}, with each call being - * for the separate jobs. - * - * <p class="note">Note that if you cancel and reschedule an already executing job, - * there may be a small period of time where {@link #onStartJob(JobParameters)} has been called for - * the newly scheduled job instance before {@link #onStopJob(JobParameters)} has been called or - * fully processed for the old job.</p> + * for the separate jobs.</p> */ public abstract class JobService extends Service { private static final String TAG = "JobService"; diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index d5a7f2851d03..28116a8df180 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -717,17 +717,9 @@ class JobConcurrencyManager { final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob() && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP; - // Avoid overlapping job execution as much as possible. - if (!isTopEj && isSimilarJobRunningLocked(nextPending)) { - if (DEBUG) { - Slog.w(TAG, "Delaying execution of job because of similarly running one: " - + nextPending); - } - // It would be nice to let the JobService running the other similar job know about - // this new job so that it doesn't unbind from the JobService and we can call - // onStartJob as soon as the older job finishes. - // TODO: optimize the job reschedule flow to reduce service binding churn - continue; + if (DEBUG && isSimilarJobRunningLocked(nextPending)) { + Slog.w(TAG, "Already running similar " + (isTopEj ? "TOP-EJ" : "job") + + " to: " + nextPending); } // Find an available slot for nextPending. The context should be one of the following: @@ -1206,13 +1198,8 @@ class JobConcurrencyManager { continue; } - // Avoid overlapping job execution as much as possible. - if (isSimilarJobRunningLocked(nextPending)) { - if (DEBUG) { - Slog.w(TAG, "Avoiding execution of job because of similarly running one: " - + nextPending); - } - continue; + if (DEBUG && isSimilarJobRunningLocked(nextPending)) { + Slog.w(TAG, "Already running similar job to: " + nextPending); } if (worker.getPreferredUid() != nextPending.getUid()) { @@ -1298,13 +1285,8 @@ class JobConcurrencyManager { continue; } - // Avoid overlapping job execution as much as possible. - if (isSimilarJobRunningLocked(nextPending)) { - if (DEBUG) { - Slog.w(TAG, "Avoiding execution of job because of similarly running one: " - + nextPending); - } - continue; + if (DEBUG && isSimilarJobRunningLocked(nextPending)) { + Slog.w(TAG, "Already running similar job to: " + nextPending); } if (isPkgConcurrencyLimitedLocked(nextPending)) { 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 cd70e88b18aa..60afdc76d249 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1208,22 +1208,12 @@ public class JobSchedulerService extends com.android.server.SystemService // This may throw a SecurityException. jobStatus.prepareLocked(); - final boolean canExecuteImmediately; if (toCancel != null) { // Implicitly replaces the existing job record with the new instance - final boolean wasJobExecuting = cancelJobImplLocked(toCancel, jobStatus, - JobParameters.STOP_REASON_CANCELLED_BY_APP, - JobParameters.INTERNAL_STOP_REASON_CANCELED, - "job rescheduled by app"); - // Avoid overlapping job executions. Don't push for immediate execution if an old - // job with the same ID was running, but let TOP EJs start immediately. - canExecuteImmediately = !wasJobExecuting - || (jobStatus.isRequestedExpeditedJob() - && mUidBiasOverride.get(jobStatus.getSourceUid(), JobInfo.BIAS_DEFAULT) - == JobInfo.BIAS_TOP_APP); + cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP, + JobParameters.INTERNAL_STOP_REASON_CANCELED, "job rescheduled by app"); } else { startTrackingJobLocked(jobStatus, null); - canExecuteImmediately = true; } if (work != null) { @@ -1266,12 +1256,7 @@ public class JobSchedulerService extends com.android.server.SystemService // list and try to run it. mJobPackageTracker.notePending(jobStatus); mPendingJobQueue.add(jobStatus); - if (canExecuteImmediately) { - // Don't ask the JobConcurrencyManager to try to run the job immediately. The - // JobServiceContext will ask the JobConcurrencyManager for another job once - // it finishes cleaning up the old job. - maybeRunPendingJobsLocked(); - } + maybeRunPendingJobsLocked(); } else { evaluateControllerStatesLocked(jobStatus); } @@ -1392,10 +1377,8 @@ public class JobSchedulerService extends com.android.server.SystemService * is null, the cancelled job is removed outright from the system. If * {@code incomingJob} is non-null, it replaces {@code cancelled} in the store of * currently scheduled jobs. - * - * @return true if the cancelled job was running */ - private boolean cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, + private void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) { if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString()); cancelled.unprepareLocked(); @@ -1406,7 +1389,7 @@ public class JobSchedulerService extends com.android.server.SystemService } mChangedJobList.remove(cancelled); // Cancel if running. - boolean wasRunning = mConcurrencyManager.stopJobOnServiceContextLocked( + mConcurrencyManager.stopJobOnServiceContextLocked( cancelled, reason, internalReasonCode, debugReason); // If this is a replacement, bring in the new version of the job if (incomingJob != null) { @@ -1414,7 +1397,6 @@ public class JobSchedulerService extends com.android.server.SystemService startTrackingJobLocked(incomingJob, cancelled); } reportActiveLocked(); - return wasRunning; } void updateUidState(int uid, int procState) { diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java index c90291e5f264..fb342b9ba9b3 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java @@ -328,11 +328,13 @@ public class AppIdleHistory { appUsageHistory.lastUsedScreenTime = getScreenOnTime(nowElapsedRealtimeMs); } - if (appUsageHistory.currentBucket > newBucket) { - appUsageHistory.currentBucket = newBucket; - logAppStandbyBucketChanged(packageName, userId, newBucket, bucketingReason); + if (appUsageHistory.currentBucket >= newBucket) { + if (appUsageHistory.currentBucket > newBucket) { + appUsageHistory.currentBucket = newBucket; + logAppStandbyBucketChanged(packageName, userId, newBucket, bucketingReason); + } + appUsageHistory.bucketingReason = bucketingReason; } - appUsageHistory.bucketingReason = bucketingReason; return appUsageHistory; } diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java index 1c4ec857538f..c9afdad8e40e 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -225,6 +225,11 @@ public class AppStandbyController | PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_SYSTEM_ONLY; + private static final int NOTIFICATION_SEEN_PROMOTED_BUCKET_FOR_PRE_T_APPS = + STANDBY_BUCKET_WORKING_SET; + private static final long NOTIFICATION_SEEN_HOLD_DURATION_FOR_PRE_T_APPS = + COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR; + // To name the lock for stack traces static class Lock {} @@ -320,11 +325,17 @@ public class AppStandbyController int mNotificationSeenPromotedBucket = ConstantsObserver.DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET; /** - * If true, tell each {@link AppIdleStateChangeListener} to give quota bump for each + * If {@code true}, tell each {@link AppIdleStateChangeListener} to give quota bump for each * notification seen event. */ private boolean mTriggerQuotaBumpOnNotificationSeen = ConstantsObserver.DEFAULT_TRIGGER_QUOTA_BUMP_ON_NOTIFICATION_SEEN; + /** + * If {@code true}, we will retain the pre-T impact of notification signal on apps targeting + * pre-T sdk levels regardless of other flag changes. + */ + boolean mRetainNotificationSeenImpactForPreTApps = + ConstantsObserver.DEFAULT_RETAIN_NOTIFICATION_SEEN_IMPACT_FOR_PRE_T_APPS; /** Minimum time a system update event should keep the buckets elevated. */ long mSystemUpdateUsageTimeoutMillis = ConstantsObserver.DEFAULT_SYSTEM_UPDATE_TIMEOUT; /** Maximum time to wait for a prediction before using simple timeouts to downgrade buckets. */ @@ -1098,17 +1109,29 @@ public class AppStandbyController final int subReason = usageEventToSubReason(eventType); final int reason = REASON_MAIN_USAGE | subReason; if (eventType == UsageEvents.Event.NOTIFICATION_SEEN) { - if (mTriggerQuotaBumpOnNotificationSeen) { - mHandler.obtainMessage(MSG_TRIGGER_LISTENER_QUOTA_BUMP, userId, -1, pkg) - .sendToTarget(); + final int notificationSeenPromotedBucket; + final long notificationSeenTimeoutMillis; + if (mRetainNotificationSeenImpactForPreTApps + && getTargetSdkVersion(pkg) < Build.VERSION_CODES.TIRAMISU) { + notificationSeenPromotedBucket = + NOTIFICATION_SEEN_PROMOTED_BUCKET_FOR_PRE_T_APPS; + notificationSeenTimeoutMillis = + NOTIFICATION_SEEN_HOLD_DURATION_FOR_PRE_T_APPS; + } else { + if (mTriggerQuotaBumpOnNotificationSeen) { + mHandler.obtainMessage(MSG_TRIGGER_LISTENER_QUOTA_BUMP, userId, -1, pkg) + .sendToTarget(); + } + notificationSeenPromotedBucket = mNotificationSeenPromotedBucket; + notificationSeenTimeoutMillis = mNotificationSeenTimeoutMillis; } // Notification-seen elevates to a higher bucket (depending on // {@link ConstantsObserver#KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET}) but doesn't // change usage time. mAppIdleHistory.reportUsage(appHistory, pkg, userId, - mNotificationSeenPromotedBucket, subReason, - 0, elapsedRealtime + mNotificationSeenTimeoutMillis); - nextCheckDelay = mNotificationSeenTimeoutMillis; + notificationSeenPromotedBucket, subReason, + 0, elapsedRealtime + notificationSeenTimeoutMillis); + nextCheckDelay = notificationSeenTimeoutMillis; } else if (eventType == UsageEvents.Event.SLICE_PINNED) { // Mild usage elevates to WORKING_SET but doesn't change usage time. mAppIdleHistory.reportUsage(appHistory, pkg, userId, @@ -1149,6 +1172,10 @@ public class AppStandbyController } } + private int getTargetSdkVersion(String packageName) { + return mInjector.getPackageManagerInternal().getPackageTargetSdkVersion(packageName); + } + /** * Returns the lowest standby bucket that is better than {@code targetBucket} and has an * valid expiry time (i.e. the expiry time is not yet elapsed). @@ -2226,6 +2253,9 @@ public class AppStandbyController pw.print(" mTriggerQuotaBumpOnNotificationSeen="); pw.print(mTriggerQuotaBumpOnNotificationSeen); pw.println(); + pw.print(" mRetainNotificationSeenImpactForPreTApps="); + pw.print(mRetainNotificationSeenImpactForPreTApps); + pw.println(); pw.print(" mSlicePinnedTimeoutMillis="); TimeUtils.formatDuration(mSlicePinnedTimeoutMillis, pw); pw.println(); @@ -2712,6 +2742,8 @@ public class AppStandbyController "notification_seen_duration"; private static final String KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET = "notification_seen_promoted_bucket"; + private static final String KEY_RETAIN_NOTIFICATION_SEEN_IMPACT_FOR_PRE_T_APPS = + "retain_notification_seen_impact_for_pre_t_apps"; private static final String KEY_TRIGGER_QUOTA_BUMP_ON_NOTIFICATION_SEEN = "trigger_quota_bump_on_notification_seen"; private static final String KEY_SLICE_PINNED_HOLD_DURATION = @@ -2773,6 +2805,7 @@ public class AppStandbyController COMPRESS_TIME ? 12 * ONE_MINUTE : 12 * ONE_HOUR; public static final int DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET = STANDBY_BUCKET_WORKING_SET; + public static final boolean DEFAULT_RETAIN_NOTIFICATION_SEEN_IMPACT_FOR_PRE_T_APPS = false; public static final boolean DEFAULT_TRIGGER_QUOTA_BUMP_ON_NOTIFICATION_SEEN = false; public static final long DEFAULT_SYSTEM_UPDATE_TIMEOUT = COMPRESS_TIME ? 2 * ONE_MINUTE : 2 * ONE_HOUR; @@ -2874,6 +2907,11 @@ public class AppStandbyController KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET, DEFAULT_NOTIFICATION_SEEN_PROMOTED_BUCKET); break; + case KEY_RETAIN_NOTIFICATION_SEEN_IMPACT_FOR_PRE_T_APPS: + mRetainNotificationSeenImpactForPreTApps = properties.getBoolean( + KEY_RETAIN_NOTIFICATION_SEEN_IMPACT_FOR_PRE_T_APPS, + DEFAULT_RETAIN_NOTIFICATION_SEEN_IMPACT_FOR_PRE_T_APPS); + break; case KEY_TRIGGER_QUOTA_BUMP_ON_NOTIFICATION_SEEN: mTriggerQuotaBumpOnNotificationSeen = properties.getBoolean( KEY_TRIGGER_QUOTA_BUMP_ON_NOTIFICATION_SEEN, diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING index e407e3126058..a6a3aafdb4f4 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING +++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING @@ -19,6 +19,18 @@ ] } ], + "presubmit-large": [ + { + "name": "CtsUsageStatsTestCases", + "options": [ + {"include-filter": "android.app.usage.cts.BroadcastResponseStatsTest"}, + {"exclude-annotation": "android.platform.test.annotations.FlakyTest"}, + {"exclude-annotation": "androidx.test.filters.FlakyTest"}, + {"exclude-annotation": "androidx.test.filters.MediumTest"}, + {"exclude-annotation": "androidx.test.filters.LargeTest"} + ] + } + ], "postsubmit": [ { "name": "CtsUsageStatsTestCases" diff --git a/core/api/test-current.txt b/core/api/test-current.txt index fe99c71d9a9a..48277fb3b488 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2923,7 +2923,7 @@ package android.view { method public static int getHoverTooltipHideTimeout(); method public static int getHoverTooltipShowTimeout(); method public static int getLongPressTooltipHideTimeout(); - method public int getPreferKeepClearForFocusDelay(); + method public boolean isPreferKeepClearForFocusEnabled(); } public class ViewDebug { diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java index 7c83d5850f76..bbe3ce3c1cdb 100644 --- a/core/java/android/app/LocaleConfig.java +++ b/core/java/android/app/LocaleConfig.java @@ -99,7 +99,7 @@ public class LocaleConfig { XmlResourceParser parser = res.getXml(resId); parseLocaleConfig(parser, res); } catch (Resources.NotFoundException e) { - Slog.w(TAG, "The resource file pointed to by the given resource ID isn't found.", e); + Slog.w(TAG, "The resource file pointed to by the given resource ID isn't found."); mStatus = STATUS_NOT_SPECIFIED; } catch (XmlPullParserException | IOException e) { Slog.w(TAG, "Failed to parse XML configuration from " diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 93f91e5af3eb..b7f113609188 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -2515,7 +2515,8 @@ public class DevicePolicyManager { * If another app already had delegated network logging access, * it will lose the delegation when a new app is delegated. * - * <p> Can only be granted by Device Owner or Profile Owner of a managed profile. + * <p> Device Owner can grant this access since Android 10. Profile Owner of a managed profile + * can grant this access since Android 12. */ public static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging"; @@ -13255,9 +13256,11 @@ public class DevicePolicyManager { * Called by a device owner, profile owner of a managed profile or delegated app with * {@link #DELEGATION_NETWORK_LOGGING} to control the network logging feature. * - * <p> Supported for a device owner from Android 8. Supported for a profile owner of a managed - * profile from Android 12. When network logging is enabled by a profile owner, the network logs - * will only include work profile network activity, not activity on the personal profile. + * <p> Supported for a device owner from Android 8 and a delegated app granted by a device + * owner from Android 10. Supported for a profile owner of a managed profile and a delegated + * app granted by a profile owner from Android 12. When network logging is enabled by a + * profile owner, the network logs will only include work profile network activity, not + * activity on the personal profile. * * <p> Network logs contain DNS lookup and connect() library call events. The following library * functions are recorded while network logging is active: diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index d71faee4cc8d..3da696ad0bc7 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -390,7 +390,8 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { * Return a string representation of this URI that has common forms of PII redacted, * making it safer to use for logging purposes. For example, {@code tel:800-466-4411} is * returned as {@code tel:xxx-xxx-xxxx} and {@code http://example.com/path/to/item/} is - * returned as {@code http://example.com/...}. + * returned as {@code http://example.com/...}. For all other uri schemes, only the scheme, + * host and port are returned. * @return the common forms PII redacted string of this URI * @hide */ @@ -398,13 +399,14 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { public @NonNull String toSafeString() { String scheme = getScheme(); String ssp = getSchemeSpecificPart(); + StringBuilder builder = new StringBuilder(64); + if (scheme != null) { + builder.append(scheme); + builder.append(":"); if (scheme.equalsIgnoreCase("tel") || scheme.equalsIgnoreCase("sip") || scheme.equalsIgnoreCase("sms") || scheme.equalsIgnoreCase("smsto") || scheme.equalsIgnoreCase("mailto") || scheme.equalsIgnoreCase("nfc")) { - StringBuilder builder = new StringBuilder(64); - builder.append(scheme); - builder.append(':'); if (ssp != null) { for (int i=0; i<ssp.length(); i++) { char c = ssp.charAt(i); @@ -415,25 +417,20 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } } } - return builder.toString(); - } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https") - || scheme.equalsIgnoreCase("ftp") || scheme.equalsIgnoreCase("rtsp")) { - ssp = "//" + ((getHost() != null) ? getHost() : "") - + ((getPort() != -1) ? (":" + getPort()) : "") - + "/..."; + } else { + // For other schemes, let's be conservative about + // the data we include -- only the host and port, not the query params, path or + // fragment, because those can often have sensitive info. + final String host = getHost(); + final int port = getPort(); + final String path = getPath(); + final String authority = getAuthority(); + if (authority != null) builder.append("//"); + if (host != null) builder.append(host); + if (port != -1) builder.append(":").append(port); + if (authority != null || path != null) builder.append("/..."); } } - // Not a sensitive scheme, but let's still be conservative about - // the data we include -- only the ssp, not the query params or - // fragment, because those can often have sensitive info. - StringBuilder builder = new StringBuilder(64); - if (scheme != null) { - builder.append(scheme); - builder.append(':'); - } - if (ssp != null) { - builder.append(ssp); - } return builder.toString(); } diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index c83869c9ee68..fb562d8e97db 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -32,7 +32,6 @@ import android.graphics.Region; import android.os.Bundle; import android.os.IRemoteCallback; import android.os.ParcelFileDescriptor; -import android.view.ContentRecordingSession; import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.IAppTransitionAnimationSpecsFuture; @@ -874,17 +873,6 @@ interface IWindowManager void detachWindowContextFromWindowContainer(IBinder clientToken); /** - * Updates the content recording session. If a different session is already in progress, then - * the pre-existing session is stopped, and the new incoming session takes over. - * - * The DisplayContent for the new session will begin recording when - * {@link RootWindowContainer#onDisplayChanged} is invoked for the new {@link VirtualDisplay}. - * - * @param incomingSession the nullable incoming content recording session - */ - void setContentRecordingSession(in ContentRecordingSession incomingSession); - - /** * Registers a listener, which is to be called whenever cross-window blur is enabled/disabled. * * @param listener the listener to be registered diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 38ca2481726b..90497e7adbf7 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -4781,9 +4781,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @UnsupportedAppUsage ListenerInfo mListenerInfo; - private boolean mPreferKeepClearForFocus; - private Runnable mMarkPreferKeepClearForFocus; - private static class TooltipInfo { /** * Text to be displayed in a tooltip popup. @@ -11962,8 +11959,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, @NonNull List<Rect> collectPreferKeepClearRects() { ListenerInfo info = mListenerInfo; - boolean keepBoundsClear = - (info != null && info.mPreferKeepClear) || mPreferKeepClearForFocus; + boolean keepClearForFocus = isFocused() + && ViewConfiguration.get(mContext).isPreferKeepClearForFocusEnabled(); + boolean keepBoundsClear = (info != null && info.mPreferKeepClear) || keepClearForFocus; boolean hasCustomKeepClearRects = info != null && info.mKeepClearRects != null; if (!keepBoundsClear && !hasCustomKeepClearRects) { @@ -11985,33 +11983,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } private void updatePreferKeepClearForFocus() { - if (mMarkPreferKeepClearForFocus != null) { - removeCallbacks(mMarkPreferKeepClearForFocus); - mMarkPreferKeepClearForFocus = null; - } - - final ViewConfiguration configuration = ViewConfiguration.get(mContext); - final int delay = configuration.getPreferKeepClearForFocusDelay(); - if (delay >= 0) { - mMarkPreferKeepClearForFocus = () -> { - mPreferKeepClearForFocus = isFocused(); - mMarkPreferKeepClearForFocus = null; - - updatePositionUpdateListener(); - post(this::updateKeepClearRects); - }; - postDelayed(mMarkPreferKeepClearForFocus, delay); + if (ViewConfiguration.get(mContext).isPreferKeepClearForFocusEnabled()) { + updatePositionUpdateListener(); + post(this::updateKeepClearRects); } } - private void cancelMarkPreferKeepClearForFocus() { - if (mMarkPreferKeepClearForFocus != null) { - removeCallbacks(mMarkPreferKeepClearForFocus); - mMarkPreferKeepClearForFocus = null; - } - mPreferKeepClearForFocus = false; - } - /** * Retrieve the list of unrestricted areas within this view's post-layout coordinate space * which the system will try to not cover with other floating elements, like the pip window. @@ -13754,7 +13731,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } invalidate(); sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED); - updatePreferKeepClearForFocus(); return true; } return false; @@ -21154,7 +21130,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, removePerformClickCallback(); clearAccessibilityThrottles(); stopNestedScroll(); - cancelMarkPreferKeepClearForFocus(); // Anything that started animating right before detach should already // be in its final state when re-attached. diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index ebc409e470e9..638b8f9f9b40 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -347,7 +347,7 @@ public class ViewConfiguration { private final long mScreenshotChordKeyTimeout; private final int mSmartSelectionInitializedTimeout; private final int mSmartSelectionInitializingTimeout; - private final int mPreferKeepClearForFocusDelay; + private final boolean mPreferKeepClearForFocusEnabled; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768915) private boolean sHasPermanentMenuKey; @@ -393,7 +393,7 @@ public class ViewConfiguration { mMinScalingSpan = 0; mSmartSelectionInitializedTimeout = SMART_SELECTION_INITIALIZED_TIMEOUT_IN_MILLISECOND; mSmartSelectionInitializingTimeout = SMART_SELECTION_INITIALIZING_TIMEOUT_IN_MILLISECOND; - mPreferKeepClearForFocusDelay = -1; + mPreferKeepClearForFocusEnabled = false; } /** @@ -508,8 +508,8 @@ public class ViewConfiguration { com.android.internal.R.integer.config_smartSelectionInitializedTimeoutMillis); mSmartSelectionInitializingTimeout = res.getInteger( com.android.internal.R.integer.config_smartSelectionInitializingTimeoutMillis); - mPreferKeepClearForFocusDelay = res.getInteger( - com.android.internal.R.integer.config_preferKeepClearForFocusDelayMillis); + mPreferKeepClearForFocusEnabled = res.getBoolean( + com.android.internal.R.bool.config_preferKeepClearForFocus); } /** @@ -1100,13 +1100,13 @@ public class ViewConfiguration { } /** - * @return The delay in milliseconds before focused Views set themselves as preferred to keep - * clear, or -1 if Views should not set themselves as preferred to keep clear. + * @return {@code true} if Views should set themselves as preferred to keep clear when focused, + * {@code false} otherwise. * @hide */ @TestApi - public int getPreferKeepClearForFocusDelay() { - return mPreferKeepClearForFocusDelay; + public boolean isPreferKeepClearForFocusEnabled() { + return mPreferKeepClearForFocusEnabled; } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index eabc13ad5ab0..127c7b7a8dc9 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -222,7 +222,6 @@ import java.io.StringWriter; import java.lang.ref.WeakReference; import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; @@ -800,8 +799,7 @@ public final class ViewRootImpl implements ViewParent, new ViewRootRectTracker(v -> v.collectPreferKeepClearRects()); private final ViewRootRectTracker mUnrestrictedKeepClearRectsTracker = new ViewRootRectTracker(v -> v.collectUnrestrictedPreferKeepClearRects()); - private List<Rect> mPendingKeepClearAreas; - private List<Rect> mPendingUnrestrictedKeepClearAreas; + private boolean mHasPendingKeepClearAreaChange; private IAccessibilityEmbeddedConnection mAccessibilityEmbeddedConnection; @@ -4819,45 +4817,31 @@ public final class ViewRootImpl implements ViewParent, } void keepClearRectsChanged() { - List<Rect> restrictedKeepClearRects = mKeepClearRectsTracker.computeChangedRects(); - List<Rect> unrestrictedKeepClearRects = - mUnrestrictedKeepClearRectsTracker.computeChangedRects(); - if ((restrictedKeepClearRects != null || unrestrictedKeepClearRects != null) - && mView != null) { - if (restrictedKeepClearRects == null) { - restrictedKeepClearRects = Collections.emptyList(); - } - if (unrestrictedKeepClearRects == null) { - unrestrictedKeepClearRects = Collections.emptyList(); - } + boolean restrictedKeepClearRectsChanged = mKeepClearRectsTracker.computeChanges(); + boolean unrestrictedKeepClearRectsChanged = + mUnrestrictedKeepClearRectsTracker.computeChanges(); - if (mHandler.hasMessages(MSG_REPORT_KEEP_CLEAR_RECTS)) { - // Keep clear areas have been reported recently, wait before reporting new set - // of keep clear areas - mPendingKeepClearAreas = restrictedKeepClearRects; - mPendingUnrestrictedKeepClearAreas = unrestrictedKeepClearRects; - } else { + if ((restrictedKeepClearRectsChanged || unrestrictedKeepClearRectsChanged) + && mView != null) { + mHasPendingKeepClearAreaChange = true; + // Only report keep clear areas immediately if they have not been reported recently + if (!mHandler.hasMessages(MSG_REPORT_KEEP_CLEAR_RECTS)) { mHandler.sendEmptyMessageDelayed(MSG_REPORT_KEEP_CLEAR_RECTS, KEEP_CLEAR_AREA_REPORT_RATE_MILLIS); - try { - mWindowSession.reportKeepClearAreasChanged(mWindow, restrictedKeepClearRects, - unrestrictedKeepClearRects); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + reportKeepClearAreasChanged(); } } } void reportKeepClearAreasChanged() { - final List<Rect> restrictedKeepClearRects = mPendingKeepClearAreas; - final List<Rect> unrestrictedKeepClearRects = mPendingUnrestrictedKeepClearAreas; - if (restrictedKeepClearRects == null && unrestrictedKeepClearRects == null) { + if (!mHasPendingKeepClearAreaChange) { return; } + mHasPendingKeepClearAreaChange = false; - mPendingKeepClearAreas = null; - mPendingUnrestrictedKeepClearAreas = null; + final List<Rect> restrictedKeepClearRects = mKeepClearRectsTracker.getLastComputedRects(); + final List<Rect> unrestrictedKeepClearRects = + mUnrestrictedKeepClearRectsTracker.getLastComputedRects(); try { mWindowSession.reportKeepClearAreasChanged(mWindow, restrictedKeepClearRects, diff --git a/core/java/android/view/ViewRootRectTracker.java b/core/java/android/view/ViewRootRectTracker.java index fd9cc1920b39..152729b8d1d8 100644 --- a/core/java/android/view/ViewRootRectTracker.java +++ b/core/java/android/view/ViewRootRectTracker.java @@ -73,10 +73,25 @@ class ViewRootRectTracker { } /** - * @return all visible rects from all views in the global (root) coordinate system + * @return all Rects from all visible Views in the global (root) coordinate system, + * or {@code null} if Rects are unchanged since the last call to this method. */ @Nullable public List<Rect> computeChangedRects() { + if (computeChanges()) { + return mRects; + } + return null; + } + + /** + * Computes changes to all Rects from all Views. + * After calling this method, the updated list of Rects can be retrieved + * with {@link #getLastComputedRects()}. + * + * @return {@code true} if there were changes, {@code false} otherwise. + */ + public boolean computeChanges() { boolean changed = mRootRectsChanged; final Iterator<ViewInfo> i = mViewInfos.iterator(); final List<Rect> rects = new ArrayList<>(mRootRects); @@ -100,10 +115,22 @@ class ViewRootRectTracker { mRootRectsChanged = false; if (!mRects.equals(rects)) { mRects = rects; - return rects; + return true; } } - return null; + return false; + } + + /** + * Returns a List of all Rects from all visible Views in the global (root) coordinate system. + * This list is only updated when calling {@link #computeChanges()} or + * {@link #computeChangedRects()}. + * + * @return all Rects from all visible Views in the global (root) coordinate system + */ + @NonNull + public List<Rect> getLastComputedRects() { + return mRects; } /** diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 2dcc58564f82..66b78f38cc0d 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -20,6 +20,7 @@ import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL; import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT; import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL; import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP; @@ -46,6 +47,7 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER; +import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER; @@ -198,6 +200,7 @@ public class InteractionJankMonitor { public static final int CUJ_SETTINGS_SLIDER = 53; public static final int CUJ_TAKE_SCREENSHOT = 54; public static final int CUJ_VOLUME_CONTROL = 55; + public static final int CUJ_SETTINGS_TOGGLE = 57; private static final int NO_STATSD_LOGGING = -1; @@ -262,6 +265,8 @@ public class InteractionJankMonitor { UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT, UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION, + UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE, }; private static volatile InteractionJankMonitor sInstance; @@ -338,6 +343,7 @@ public class InteractionJankMonitor { CUJ_SETTINGS_SLIDER, CUJ_TAKE_SCREENSHOT, CUJ_VOLUME_CONTROL, + CUJ_SETTINGS_TOGGLE, }) @Retention(RetentionPolicy.SOURCE) public @interface CujType { @@ -768,6 +774,8 @@ public class InteractionJankMonitor { return "TAKE_SCREENSHOT"; case CUJ_VOLUME_CONTROL: return "VOLUME_CONTROL"; + case CUJ_SETTINGS_TOGGLE: + return "SETTINGS_TOGGLE"; } return "UNKNOWN"; } diff --git a/core/java/com/android/internal/os/BatteryUsageStatsStore.java b/core/java/com/android/internal/os/BatteryUsageStatsStore.java index 09c9cfcd2e71..04f72c7d703d 100644 --- a/core/java/com/android/internal/os/BatteryUsageStatsStore.java +++ b/core/java/com/android/internal/os/BatteryUsageStatsStore.java @@ -320,4 +320,19 @@ public class BatteryUsageStatsStore { mFileSizes.remove(file); } } + + public void removeAllSnapshots() { + lockSnapshotDirectory(); + try { + for (File file : mStoreDir.listFiles()) { + if (file.getName().endsWith(SNAPSHOT_FILE_EXTENSION)) { + if (!file.delete()) { + Slog.e(TAG, "Cannot delete battery usage stats " + file); + } + } + } + } finally { + unlockSnapshotDirectory(); + } + } } diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto index c4ff49cfbfaf..00127c134ce6 100644 --- a/core/proto/android/server/jobscheduler.proto +++ b/core/proto/android/server/jobscheduler.proto @@ -1073,6 +1073,7 @@ message JobStatusDumpProto { RARE = 3; NEVER = 4; RESTRICTED = 5; + EXEMPTED = 6; } optional Bucket standby_bucket = 17; optional bool is_exempted_from_app_standby = 27; diff --git a/core/res/res/color/hint_foreground_material_dark.xml b/core/res/res/color/hint_foreground_material_dark.xml index 5cc955952524..66fcb041dd7a 100644 --- a/core/res/res/color/hint_foreground_material_dark.xml +++ b/core/res/res/color/hint_foreground_material_dark.xml @@ -15,10 +15,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="true" - android:state_pressed="true" - android:alpha="@dimen/hint_pressed_alpha_material_dark" - android:color="@color/foreground_material_dark" /> - <item android:alpha="@dimen/hint_alpha_material_dark" + <item android:alpha="@dimen/secondary_content_alpha_material_dark" android:color="@color/foreground_material_dark" /> </selector> diff --git a/core/res/res/color/hint_foreground_material_light.xml b/core/res/res/color/hint_foreground_material_light.xml index f7465e0e5139..63dd3b0131ca 100644 --- a/core/res/res/color/hint_foreground_material_light.xml +++ b/core/res/res/color/hint_foreground_material_light.xml @@ -15,10 +15,6 @@ --> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="true" - android:state_pressed="true" - android:alpha="@dimen/hint_pressed_alpha_material_light" - android:color="@color/foreground_material_light" /> - <item android:alpha="@dimen/hint_alpha_material_light" + <item android:alpha="@dimen/secondary_content_alpha_material_light" android:color="@color/foreground_material_light" /> </selector> diff --git a/core/res/res/layout/log_access_user_consent_dialog_permission.xml b/core/res/res/layout/log_access_user_consent_dialog_permission.xml index c88bc9282708..1a395b9fc30f 100644 --- a/core/res/res/layout/log_access_user_consent_dialog_permission.xml +++ b/core/res/res/layout/log_access_user_consent_dialog_permission.xml @@ -26,8 +26,7 @@ android:paddingLeft="24dp" android:paddingRight="24dp" android:paddingTop="24dp" - android:paddingBottom="24dp" - android:background="?attr/colorSurface"> + android:paddingBottom="24dp"> <ImageView android:id="@+id/log_access_image_view" @@ -37,8 +36,7 @@ android:src="@drawable/ic_doc_document" tools:layout_editor_absoluteX="148dp" tools:layout_editor_absoluteY="35dp" - android:gravity="center" - android:tint="?attr/colorAccentPrimaryVariant"/> + android:gravity="center" /> <TextView android:id="@+id/log_access_dialog_title" @@ -60,29 +58,35 @@ android:textColor="?android:attr/textColorPrimary" android:gravity="center" /> - <Button - android:id="@+id/log_access_dialog_allow_button" + <LinearLayout + android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="match_parent" - android:text="@string/log_access_confirmation_allow" - style="@style/PermissionGrantButtonTop" - android:layout_marginBottom="5dp" - android:layout_centerHorizontal="true" - android:layout_alignParentTop="true" - android:layout_alignParentBottom="true" - android:clipToOutline="true" - android:gravity="center" /> - - <Button - android:id="@+id/log_access_dialog_deny_button" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:text="@string/log_access_confirmation_deny" - style="@style/PermissionGrantButtonBottom" - android:layout_centerHorizontal="true" - android:layout_alignParentTop="true" - android:layout_alignParentBottom="true" - android:clipToOutline="true" - android:gravity="center" /> + android:layout_height="wrap_content"> + <Button + android:id="@+id/log_access_dialog_allow_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/log_access_confirmation_allow" + style="@style/PermissionGrantButtonTop" + android:textAppearance="@style/PermissionGrantButtonTextAppearance" + android:layout_marginBottom="5dp" + android:layout_centerHorizontal="true" + android:layout_alignParentTop="true" + android:layout_alignParentBottom="true" + android:clipToOutline="true" + android:gravity="center" /> + <Button + android:id="@+id/log_access_dialog_deny_button" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/log_access_confirmation_deny" + style="@style/PermissionGrantButtonBottom" + android:textAppearance="@style/PermissionGrantButtonTextAppearance" + android:layout_centerHorizontal="true" + android:layout_alignParentTop="true" + android:layout_alignParentBottom="true" + android:clipToOutline="true" + android:gravity="center" /> + </LinearLayout> </LinearLayout>
\ No newline at end of file diff --git a/core/res/res/layout/search_view.xml b/core/res/res/layout/search_view.xml index 0c462fda9235..39034dc61620 100644 --- a/core/res/res/layout/search_view.xml +++ b/core/res/res/layout/search_view.xml @@ -96,8 +96,8 @@ android:id="@+id/search_close_btn" android:layout_width="wrap_content" android:layout_height="match_parent" - android:paddingStart="8dip" - android:paddingEnd="8dip" + android:paddingStart="12dip" + android:paddingEnd="12dip" android:layout_gravity="center_vertical" android:background="?attr/selectableItemBackgroundBorderless" android:focusable="true" diff --git a/core/res/res/values-television/styles.xml b/core/res/res/values-television/styles.xml new file mode 100644 index 000000000000..ad9140cdc6a9 --- /dev/null +++ b/core/res/res/values-television/styles.xml @@ -0,0 +1,21 @@ +<!-- + ~ Copyright (C) 2022 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<resources> + <style name="PermissionGrantButtonTop" + parent="@style/Widget.Leanback.Button.ButtonBarGravityStart" /> + <style name="PermissionGrantButtonBottom" + parent="@style/Widget.Leanback.Button.ButtonBarGravityStart" /> +</resources>
\ No newline at end of file diff --git a/core/res/res/values/colors_material.xml b/core/res/res/values/colors_material.xml index d357f0102777..ea6e1f182fbf 100644 --- a/core/res/res/values/colors_material.xml +++ b/core/res/res/values/colors_material.xml @@ -65,18 +65,12 @@ <!-- 70% white --> <color name="secondary_text_default_material_dark">#b3ffffff</color> - <item name="hint_alpha_material_dark" format="float" type="dimen">0.50</item> - <item name="hint_alpha_material_light" format="float" type="dimen">0.38</item> - - <item name="hint_pressed_alpha_material_dark" format="float" type="dimen">0.70</item> - <item name="hint_pressed_alpha_material_light" format="float" type="dimen">0.54</item> - <item name="disabled_alpha_material_light" format="float" type="dimen">0.26</item> <item name="disabled_alpha_material_dark" format="float" type="dimen">0.30</item> <item name="primary_content_alpha_material_dark" format="float" type="dimen">1</item> <item name="primary_content_alpha_material_light" format="float" type="dimen">0.87</item> <item name="secondary_content_alpha_material_dark" format="float" type="dimen">.7</item> - <item name="secondary_content_alpha_material_light" format="float" type="dimen">0.54</item> + <item name="secondary_content_alpha_material_light" format="float" type="dimen">0.60</item> <item name="highlight_alpha_material_light" format="float" type="dimen">0.10</item> <item name="highlight_alpha_material_dark" format="float" type="dimen">0.10</item> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 054695ef8f93..70766cc79eb4 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5163,9 +5163,8 @@ when TextClassifier has not been initialized. --> <integer name="config_smartSelectionInitializingTimeoutMillis">500</integer> - <!-- The delay in milliseconds before focused Views set themselves as preferred to keep clear. - Set to -1 if Views should not set themselves as preferred to keep clear. --> - <integer name="config_preferKeepClearForFocusDelayMillis">-1</integer> + <!-- If true, Views will declare they prefer to be kept clear from overlays when focused. --> + <bool name="config_preferKeepClearForFocus">false</bool> <!-- Indicates that default fitness tracker app needs to request sensor and location permissions. --> <bool name="config_trackerAppNeedsPermissions">false</bool> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 0c35f93147a6..cfc298c74110 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -1526,26 +1526,27 @@ please see styles_device_defaults.xml. <!-- The style for log access consent text --> <style name="AllowLogAccess"> - <item name="android:layout_width">332dp</item> <item name="android:textSize">24sp</item> <item name="android:fontFamily">google-sans</item> </style> <style name="PrimaryAllowLogAccess"> - <item name="android:layout_width">332dp</item> <item name="android:textSize">14sp</item> <item name="android:fontFamily">google-sans-text</item> </style> + <style name="PermissionGrantButtonTextAppearance"> + <item name="android:fontFamily">google-sans-medium</item> + <item name="android:textSize">14sp</item> + <item name="android:textColor">@android:color/system_neutral1_900</item> + </style> + <style name="PermissionGrantButtonTop" parent="@android:style/Widget.DeviceDefault.Button.Borderless.Colored"> <item name="android:layout_width">332dp</item> <item name="android:layout_height">56dp</item> <item name="android:layout_marginTop">2dp</item> <item name="android:layout_marginBottom">2dp</item> - <item name="android:fontFamily">google-sans-medium</item> - <item name="android:textSize">14sp</item> - <item name="android:textColor">@android:color/system_neutral1_900</item> <item name="android:background">@drawable/grant_permissions_buttons_top</item> </style> @@ -1555,9 +1556,6 @@ please see styles_device_defaults.xml. <item name="android:layout_height">56dp</item> <item name="android:layout_marginTop">2dp</item> <item name="android:layout_marginBottom">2dp</item> - <item name="android:fontFamily">google-sans-medium</item> - <item name="android:textSize">14sp</item> - <item name="android:textColor">@android:color/system_neutral1_900</item> <item name="android:background">@drawable/grant_permissions_buttons_bottom</item> </style> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 5bfc56888789..e111ee184be1 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -479,7 +479,7 @@ <java-symbol type="bool" name="config_useAssistantVolume" /> <java-symbol type="integer" name="config_smartSelectionInitializedTimeoutMillis" /> <java-symbol type="integer" name="config_smartSelectionInitializingTimeoutMillis" /> - <java-symbol type="integer" name="config_preferKeepClearForFocusDelayMillis" /> + <java-symbol type="bool" name="config_preferKeepClearForFocus" /> <java-symbol type="bool" name="config_hibernationDeletesOatArtifactsEnabled"/> <java-symbol type="integer" name="config_defaultAnalogClockSecondsHandFps"/> diff --git a/core/tests/coretests/src/android/net/UriTest.java b/core/tests/coretests/src/android/net/UriTest.java index 3733bfa586d1..89632a46267e 100644 --- a/core/tests/coretests/src/android/net/UriTest.java +++ b/core/tests/coretests/src/android/net/UriTest.java @@ -989,10 +989,14 @@ public class UriTest extends TestCase { checkToSafeString("ftp://ftp.android.com:2121/...", "ftp://root:love@ftp.android.com:2121/"); - checkToSafeString("unsupported://ajkakjah/askdha/secret?secret", + checkToSafeString("unsupported://ajkakjah/...", "unsupported://ajkakjah/askdha/secret?secret"); - checkToSafeString("unsupported:ajkakjah/askdha/secret?secret", + checkToSafeString("unsupported:", "unsupported:ajkakjah/askdha/secret?secret"); + checkToSafeString("unsupported:/...", + "unsupported:/ajkakjah/askdha/secret?secret"); + checkToSafeString("file:///...", + "file:///path/to/secret.doc"); } private void checkToSafeString(String expectedSafeString, String original) { diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java index 21f6e7c82d7f..c9729fab3b5e 100644 --- a/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java +++ b/core/tests/coretests/src/com/android/internal/os/BatteryUsageStatsStoreTest.java @@ -132,6 +132,26 @@ public class BatteryUsageStatsStoreTest { } @Test + public void testRemoveAllSnapshots() throws Exception { + prepareBatteryStats(); + + for (int i = 0; i < 3; i++) { + mMockClock.realtime += 10_000_000; + mMockClock.uptime += 10_000_000; + mMockClock.currentTime += 10_000_000; + prepareBatteryStats(); + + mBatteryStats.resetAllStatsCmdLocked(); + } + + assertThat(getDirectorySize(mStoreDirectory)).isNotEqualTo(0); + + mBatteryUsageStatsStore.removeAllSnapshots(); + + assertThat(getDirectorySize(mStoreDirectory)).isEqualTo(0); + } + + @Test public void testSavingStatsdAtomPullTimestamp() { mBatteryUsageStatsStore.setLastBatteryUsageStatsBeforeResetAtomPullTimestamp(1234); assertThat(mBatteryUsageStatsStore.getLastBatteryUsageStatsBeforeResetAtomPullTimestamp()) diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl index d190fcec6c8c..1d58a409718d 100644 --- a/media/java/android/media/projection/IMediaProjectionManager.aidl +++ b/media/java/android/media/projection/IMediaProjectionManager.aidl @@ -21,6 +21,7 @@ import android.media.projection.IMediaProjectionCallback; import android.media.projection.IMediaProjectionWatcherCallback; import android.media.projection.MediaProjectionInfo; import android.os.IBinder; +import android.view.ContentRecordingSession; /** {@hide} */ interface IMediaProjectionManager { @@ -33,4 +34,15 @@ interface IMediaProjectionManager { void stopActiveProjection(); void addCallback(IMediaProjectionWatcherCallback callback); void removeCallback(IMediaProjectionWatcherCallback callback); + + /** + * Updates the content recording session. If a different session is already in progress, then + * the pre-existing session is stopped, and the new incoming session takes over. Only updates + * the session if the given projection is valid. + * + * @param incomingSession the nullable incoming content recording session + * @param projection the non-null projection the session describes + */ + void setContentRecordingSession(in ContentRecordingSession incomingSession, + in IMediaProjection projection); } diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java index b5f95938f845..ba7bf3f5f5d3 100644 --- a/media/java/android/media/projection/MediaProjection.java +++ b/media/java/android/media/projection/MediaProjection.java @@ -26,12 +26,11 @@ import android.hardware.display.VirtualDisplay; import android.hardware.display.VirtualDisplayConfig; import android.os.Handler; import android.os.RemoteException; +import android.os.ServiceManager; import android.util.ArrayMap; import android.util.Log; import android.view.ContentRecordingSession; -import android.view.IWindowManager; import android.view.Surface; -import android.view.WindowManagerGlobal; import android.window.WindowContainerToken; import java.util.Map; @@ -53,6 +52,7 @@ public final class MediaProjection { private final IMediaProjection mImpl; private final Context mContext; private final Map<Callback, CallbackRecord> mCallbacks; + @Nullable private IMediaProjectionManager mProjectionService = null; /** @hide */ public MediaProjection(Context context, IMediaProjection impl) { @@ -172,7 +172,6 @@ public final class MediaProjection { @NonNull VirtualDisplayConfig.Builder virtualDisplayConfig, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) { try { - final IWindowManager wmService = WindowManagerGlobal.getWindowManagerService(); final WindowContainerToken taskWindowContainerToken = mImpl.getTaskRecordingWindowContainerToken(); Context windowContext = null; @@ -199,7 +198,7 @@ public final class MediaProjection { } session.setDisplayId(virtualDisplay.getDisplay().getDisplayId()); // Successfully set up, so save the current session details. - wmService.setContentRecordingSession(session); + getProjectionService().setContentRecordingSession(session, mImpl); return virtualDisplay; } catch (RemoteException e) { // Can not capture if WMS is not accessible, so bail out. @@ -207,6 +206,14 @@ public final class MediaProjection { } } + private IMediaProjectionManager getProjectionService() { + if (mProjectionService == null) { + mProjectionService = IMediaProjectionManager.Stub.asInterface( + ServiceManager.getService(Context.MEDIA_PROJECTION_SERVICE)); + } + return mProjectionService; + } + /** * Stops projection. */ diff --git a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml index 5e3907caf860..5411591d8df6 100644 --- a/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml +++ b/packages/SettingsLib/SettingsTheme/res/values-night-v31/colors.xml @@ -48,8 +48,6 @@ <color name="settingslib_text_color_preference_category_title">@android:color/system_accent1_100</color> - <color name="settingslib_ripple_color">@color/settingslib_material_grey_900</color> - <color name="settingslib_surface_dark">@android:color/system_neutral1_800</color> <color name="settingslib_colorSurface">@color/settingslib_surface_dark</color> diff --git a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java index b43b44421fb8..fb06976ebfe3 100644 --- a/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/PrimarySwitchPreference.java @@ -19,8 +19,6 @@ package com.android.settingslib; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnClickListener; import android.widget.Switch; import androidx.annotation.Keep; @@ -28,6 +26,7 @@ import androidx.annotation.Nullable; import androidx.preference.PreferenceViewHolder; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import com.android.settingslib.core.instrumentation.SettingsJankMonitor; /** * A custom preference that provides inline switch toggle. It has a mandatory field for title, and @@ -65,31 +64,25 @@ public class PrimarySwitchPreference extends RestrictedPreference { @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); - final View switchWidget = holder.findViewById(R.id.switchWidget); - if (switchWidget != null) { - switchWidget.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (mSwitch != null && !mSwitch.isEnabled()) { - return; - } - setChecked(!mChecked); - if (!callChangeListener(mChecked)) { - setChecked(!mChecked); - } else { - persistBoolean(mChecked); - } + mSwitch = (Switch) holder.findViewById(R.id.switchWidget); + if (mSwitch != null) { + mSwitch.setOnClickListener(v -> { + if (mSwitch != null && !mSwitch.isEnabled()) { + return; + } + final boolean newChecked = !mChecked; + if (callChangeListener(newChecked)) { + SettingsJankMonitor.detectToggleJank(getKey(), mSwitch); + setChecked(newChecked); + persistBoolean(newChecked); } }); // Consumes move events to ignore drag actions. - switchWidget.setOnTouchListener((v, event) -> { + mSwitch.setOnTouchListener((v, event) -> { return event.getActionMasked() == MotionEvent.ACTION_MOVE; }); - } - mSwitch = (Switch) holder.findViewById(R.id.switchWidget); - if (mSwitch != null) { mSwitch.setContentDescription(getTitle()); mSwitch.setChecked(mChecked); mSwitch.setEnabled(mEnableSwitch); diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 2f30baa79b9d..cb8e7e8bb6ef 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -198,10 +198,9 @@ public class RestrictedPreferenceHelper { if (mDisabledByAdmin != disabled) { mDisabledByAdmin = disabled; changed = true; + updateDisabledState(); } - updateDisabledState(); - return changed; } @@ -210,10 +209,9 @@ public class RestrictedPreferenceHelper { if (mDisabledByAppOps != disabled) { mDisabledByAppOps = disabled; changed = true; + updateDisabledState(); } - updateDisabledState(); - return changed; } diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index e6160bb9896d..b5e4fa38d244 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -254,9 +254,11 @@ public class RestrictedSwitchPreference extends SwitchPreference { final boolean ecmEnabled = getContext().getResources().getBoolean( com.android.internal.R.bool.config_enhancedConfirmationModeEnabled); final boolean appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED; - if (appOpsAllowed || isEnabled) { + if (isEnabled) { setEnabled(true); - } else { + } else if (appOpsAllowed && isDisabledByAppOps()) { + setEnabled(true); + } else if (!appOpsAllowed){ setDisabledByAppOps(true); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt new file mode 100644 index 000000000000..a5f69ffec4b4 --- /dev/null +++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SettingsJankMonitor.kt @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settingslib.core.instrumentation + +import android.view.View +import androidx.annotation.VisibleForTesting +import androidx.preference.PreferenceGroupAdapter +import androidx.preference.SwitchPreference +import androidx.recyclerview.widget.RecyclerView +import com.android.internal.jank.InteractionJankMonitor +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +/** + * Helper class for Settings library to trace jank. + */ +object SettingsJankMonitor { + private val jankMonitor = InteractionJankMonitor.getInstance() + private val scheduledExecutorService = Executors.newSingleThreadScheduledExecutor() + + // Switch toggle animation duration is 250ms, and there is also a ripple effect animation when + // clicks, which duration is variable. Use 300ms here to cover. + @VisibleForTesting + const val MONITORED_ANIMATION_DURATION_MS = 300L + + /** + * Detects the jank when click on a SwitchPreference. + * + * @param recyclerView the recyclerView contains the preference + * @param preference the clicked preference + */ + @JvmStatic + fun detectSwitchPreferenceClickJank(recyclerView: RecyclerView, preference: SwitchPreference) { + val adapter = recyclerView.adapter as? PreferenceGroupAdapter ?: return + val adapterPosition = adapter.getPreferenceAdapterPosition(preference) + val viewHolder = recyclerView.findViewHolderForAdapterPosition(adapterPosition) ?: return + detectToggleJank(preference.key, viewHolder.itemView) + } + + /** + * Detects the animation jank on the given view. + * + * @param tag the tag for jank monitor + * @param view the instrumented view + */ + @JvmStatic + fun detectToggleJank(tag: String?, view: View) { + val builder = InteractionJankMonitor.Configuration.Builder.withView( + InteractionJankMonitor.CUJ_SETTINGS_TOGGLE, + view + ) + if (tag != null) { + builder.setTag(tag) + } + if (jankMonitor.begin(builder)) { + scheduledExecutorService.schedule({ + jankMonitor.end(InteractionJankMonitor.CUJ_SETTINGS_TOGGLE) + }, MONITORED_ANIMATION_DURATION_MS, TimeUnit.MILLISECONDS) + } + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/tests/robotests/Android.bp b/packages/SettingsLib/tests/robotests/Android.bp index 2d1a516f4f11..5c55a435b463 100644 --- a/packages/SettingsLib/tests/robotests/Android.bp +++ b/packages/SettingsLib/tests/robotests/Android.bp @@ -63,6 +63,7 @@ java_library { libs: [ "Robolectric_all-target", + "mockito-robolectric-prebuilt", "truth-prebuilt", ], } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java index 9c16740061fe..74c2fc8cce4c 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/PrimarySwitchPreferenceTest.java @@ -30,14 +30,17 @@ import androidx.preference.Preference.OnPreferenceChangeListener; import androidx.preference.PreferenceViewHolder; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; +import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowInteractionJankMonitor.class}) public class PrimarySwitchPreferenceTest { private Context mContext; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java new file mode 100644 index 000000000000..d67d44b9035d --- /dev/null +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.core.instrumentation; + +import static com.android.internal.jank.InteractionJankMonitor.CUJ_SETTINGS_TOGGLE; +import static com.android.settingslib.core.instrumentation.SettingsJankMonitor.MONITORED_ANIMATION_DURATION_MS; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.NonNull; +import android.view.View; + +import androidx.preference.PreferenceGroupAdapter; +import androidx.preference.SwitchPreference; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.jank.InteractionJankMonitor.CujType; +import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; +import org.robolectric.util.ReflectionHelpers; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowInteractionJankMonitor.class, SettingsJankMonitorTest.ShadowBuilder.class}) +public class SettingsJankMonitorTest { + private static final String TEST_KEY = "key"; + + @Rule + public MockitoRule mocks = MockitoJUnit.rule(); + + @Mock + private View mView; + + @Mock + private RecyclerView mRecyclerView; + + @Mock + private PreferenceGroupAdapter mPreferenceGroupAdapter; + + @Mock + private SwitchPreference mSwitchPreference; + + @Mock + private ScheduledExecutorService mScheduledExecutorService; + + @Before + public void setUp() { + ShadowInteractionJankMonitor.reset(); + when(ShadowInteractionJankMonitor.MOCK_INSTANCE.begin(any())).thenReturn(true); + ReflectionHelpers.setStaticField(SettingsJankMonitor.class, "scheduledExecutorService", + mScheduledExecutorService); + } + + @Test + public void detectToggleJank() { + SettingsJankMonitor.detectToggleJank(TEST_KEY, mView); + + verifyToggleJankMonitored(); + } + + @Test + public void detectSwitchPreferenceClickJank() { + int adapterPosition = 7; + when(mRecyclerView.getAdapter()).thenReturn(mPreferenceGroupAdapter); + when(mPreferenceGroupAdapter.getPreferenceAdapterPosition(mSwitchPreference)) + .thenReturn(adapterPosition); + when(mRecyclerView.findViewHolderForAdapterPosition(adapterPosition)) + .thenReturn(new RecyclerView.ViewHolder(mView) { + }); + when(mSwitchPreference.getKey()).thenReturn(TEST_KEY); + + SettingsJankMonitor.detectSwitchPreferenceClickJank(mRecyclerView, mSwitchPreference); + + verifyToggleJankMonitored(); + } + + private void verifyToggleJankMonitored() { + verify(ShadowInteractionJankMonitor.MOCK_INSTANCE).begin(ShadowBuilder.sBuilder); + assertThat(ShadowBuilder.sView).isSameInstanceAs(mView); + ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class); + verify(mScheduledExecutorService).schedule(runnableCaptor.capture(), + eq(MONITORED_ANIMATION_DURATION_MS), eq(TimeUnit.MILLISECONDS)); + runnableCaptor.getValue().run(); + verify(ShadowInteractionJankMonitor.MOCK_INSTANCE).end(CUJ_SETTINGS_TOGGLE); + } + + @Implements(InteractionJankMonitor.Configuration.Builder.class) + static class ShadowBuilder { + private static InteractionJankMonitor.Configuration.Builder sBuilder; + private static View sView; + + @Resetter + public static void reset() { + sBuilder = null; + sView = null; + } + + @Implementation + public static InteractionJankMonitor.Configuration.Builder withView( + @CujType int cuj, @NonNull View view) { + assertThat(cuj).isEqualTo(CUJ_SETTINGS_TOGGLE); + sView = view; + sBuilder = mock(InteractionJankMonitor.Configuration.Builder.class); + when(sBuilder.setTag(TEST_KEY)).thenReturn(sBuilder); + return sBuilder; + } + } +} diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowInteractionJankMonitor.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowInteractionJankMonitor.java new file mode 100644 index 000000000000..855da16b1dfa --- /dev/null +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowInteractionJankMonitor.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.testutils.shadow; + +import static org.mockito.Mockito.mock; + +import com.android.internal.jank.InteractionJankMonitor; + +import org.mockito.Mockito; +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.annotation.Resetter; + +@Implements(InteractionJankMonitor.class) +public class ShadowInteractionJankMonitor { + public static final InteractionJankMonitor MOCK_INSTANCE = mock(InteractionJankMonitor.class); + + @Resetter + public static void reset() { + Mockito.reset(MOCK_INSTANCE); + } + + @Implementation + public static InteractionJankMonitor getInstance() { + return MOCK_INSTANCE; + } +} diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING index d146fc91bfb5..b01fd6de5243 100644 --- a/packages/SystemUI/TEST_MAPPING +++ b/packages/SystemUI/TEST_MAPPING @@ -124,49 +124,5 @@ } ] } - ], - "hubui-postsubmit": [ - { - "name": "PlatformScenarioTests", - "options": [ - { - "include-filter": "android.platform.test.scenario.hubui" - }, - { - "include-annotation": "android.platform.test.scenario.annotation.HubUi" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] - } - ], - "hubui-presubmit": [ - { - "name": "PlatformScenarioTests", - "options": [ - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "include-annotation": "android.platform.test.scenario.annotation.HubUi" - }, - { - "include-filter": "android.platform.test.scenario.hubui" - }, - { - "exclude-annotation": "org.junit.Ignore" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "android.platform.test.annotations.Postsubmit" - } - ] - } ] } diff --git a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml index 1c09e81f92ca..18f870d27772 100644 --- a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml +++ b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml @@ -58,6 +58,7 @@ android:id="@+id/edit_text" android:layout_width="match_parent" android:layout_height="wrap_content" + android:minHeight="48dp" android:background="@null" android:gravity="start|top" android:textSize="24sp" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 7010a289df68..1351ec144ec5 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2485,7 +2485,7 @@ <!-- Text informing user where text being edited was copied from [CHAR LIMIT=NONE] --> <string name="clipboard_edit_source">From <xliff:g id="appName" example="Gmail">%1$s</xliff:g></string> <!-- Label for button to dismiss clipboard overlay [CHAR LIMIT=NONE] --> - <string name="clipboard_dismiss_description">Dismiss copy UI</string> + <string name="clipboard_dismiss_description">Dismiss copied text</string> <!-- Label for button to edit text that was copied to the clipboard [CHAR LIMIT=NONE] --> <string name="clipboard_edit_text_description">Edit copied text</string> <!-- Label for button to edit an image that was copied to the clipboard [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt index 44879aa9e85b..b8a00133c728 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt @@ -261,7 +261,7 @@ class ControlActionCoordinatorImpl @Inject constructor( taskViewFactory.get().create(context, uiExecutor, { dialog = DetailDialog( activityContext, broadcastSender, - it, pendingIntent, cvh + it, pendingIntent, cvh, keyguardStateController, activityStarter ).also { it.setOnDismissListener { _ -> dialog = null } it.show() diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt index 80589a2711cc..edd1c6891946 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt @@ -34,6 +34,8 @@ import android.widget.ImageView import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.R import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.wm.shell.TaskView /** @@ -46,7 +48,9 @@ class DetailDialog( val broadcastSender: BroadcastSender, val taskView: TaskView, val pendingIntent: PendingIntent, - val cvh: ControlViewHolder + val cvh: ControlViewHolder, + val keyguardStateController: KeyguardStateController, + val activityStarter: ActivityStarter ) : Dialog( activityContext, R.style.Theme_SystemUI_Dialog_Control_DetailPanel @@ -145,12 +149,25 @@ class DetailDialog( requireViewById<ImageView>(R.id.control_detail_open_in_app).apply { setOnClickListener { v: View -> - // Remove the task explicitly, since onRelease() callback will be executed after - // startActivity() below is called. removeDetailTask() dismiss() - broadcastSender.closeSystemDialogs() - pendingIntent.send() + + val action = ActivityStarter.OnDismissAction { + // Remove the task explicitly, since onRelease() callback will be executed after + // startActivity() below is called. + broadcastSender.closeSystemDialogs() + pendingIntent.send() + false + } + if (keyguardStateController.isUnlocked()) { + action.onDismiss() + } else { + activityStarter.dismissKeyguardThenExecute( + action, + null /* cancel */, + true /* afterKeyguardGone */ + ) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt index 0f8687183e94..e9caaaf54422 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaCarouselController.kt @@ -150,12 +150,13 @@ class MediaCarouselController @Inject constructor( } private val configListener = object : ConfigurationController.ConfigurationListener { override fun onDensityOrFontScaleChanged() { - recreatePlayers() + // System font changes should only happen when UMO is offscreen or a flicker may occur + updatePlayers(recreateMedia = true) inflateSettingsButton() } override fun onThemeChanged() { - recreatePlayers() + updatePlayers(recreateMedia = false) inflateSettingsButton() } @@ -165,7 +166,7 @@ class MediaCarouselController @Inject constructor( } override fun onUiModeChanged() { - recreatePlayers() + updatePlayers(recreateMedia = false) inflateSettingsButton() } } @@ -539,7 +540,7 @@ class MediaCarouselController @Inject constructor( } } - private fun recreatePlayers() { + private fun updatePlayers(recreateMedia: Boolean) { pageIndicator.tintList = ColorStateList.valueOf( context.getColor(R.color.media_paging_indicator) ) @@ -554,7 +555,9 @@ class MediaCarouselController @Inject constructor( } } else { val isSsReactivated = MediaPlayerData.isSsReactivated(key) - removePlayer(key, dismissMediaData = false, dismissRecommendation = false) + if (recreateMedia) { + removePlayer(key, dismissMediaData = false, dismissRecommendation = false) + } addOrUpdatePlayer( key = key, oldKey = null, data = data, isSsReactivated = isSsReactivated) } @@ -945,6 +948,7 @@ internal object MediaPlayerData { .thenByDescending { shouldPrioritizeSs == it.isSsMediaRec } .thenByDescending { !it.data.resumption } .thenByDescending { it.data.playbackLocation != MediaData.PLAYBACK_CAST_REMOTE } + .thenByDescending { it.data.lastActive } .thenByDescending { it.updateTime } .thenByDescending { it.data.notificationKey } diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java index 429f2df6b50c..731e17742538 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java @@ -292,10 +292,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { return; } - final int warningLevel = mContext.getResources().getInteger( - com.android.internal.R.integer.config_lowBatteryWarningLevel); final String percentage = NumberFormat.getPercentInstance() - .format((double) warningLevel / 100.0); + .format((double) mCurrentBatterySnapshot.getBatteryLevel() / 100.0); final String title = mContext.getString(R.string.battery_low_title); final String contentText = mContext.getString( R.string.battery_low_description, percentage); @@ -309,7 +307,6 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { .setContentText(contentText) .setContentTitle(title) .setOnlyAlertOnce(true) - .setOngoing(true) .setStyle(new Notification.BigTextStyle().bigText(contentText)) .setVisibility(Notification.VISIBILITY_PUBLIC); if (hasBatterySettings()) { @@ -340,8 +337,7 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI { } private boolean showSevereLowBatteryDialog() { - final boolean isSevereState = !mCurrentBatterySnapshot.isHybrid() || mBucket < -1; - return isSevereState && mUseSevereDialog; + return mBucket < -1 && mUseSevereDialog; } /** diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java index dcdd784bd2a1..67dae9e7a0ea 100644 --- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java +++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java @@ -359,11 +359,7 @@ public class PowerUI extends CoreStartable implements CommandQueue.Callbacks { } mWarnings.updateSnapshot(mCurrentBatteryStateSnapshot); - if (mCurrentBatteryStateSnapshot.isHybrid()) { - maybeShowHybridWarning(mCurrentBatteryStateSnapshot, mLastBatteryStateSnapshot); - } else { - maybeShowBatteryWarning(mCurrentBatteryStateSnapshot, mLastBatteryStateSnapshot); - } + maybeShowHybridWarning(mCurrentBatteryStateSnapshot, mLastBatteryStateSnapshot); } // updates the time estimate if we don't have one or battery level has changed. diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java index 90a3d4586fd3..f1fdae7db482 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java @@ -451,11 +451,11 @@ public class InternetDialogController implements AccessPointController.AccessPoi final SignalStrength strength = mTelephonyManager.getSignalStrength(); int level = (strength == null) ? 0 : strength.getLevel(); int numLevels = SignalStrength.NUM_SIGNAL_STRENGTH_BINS; - if ((mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) - || isCarrierNetworkActive) { - level = isCarrierNetworkActive - ? SignalStrength.NUM_SIGNAL_STRENGTH_BINS - : (level + 1); + if (isCarrierNetworkActive) { + level = getCarrierNetworkLevel(); + numLevels = WifiEntry.WIFI_LEVEL_MAX + 1; + } else if (mSubscriptionManager != null && shouldInflateSignalStrength(mDefaultDataSubId)) { + level += 1; numLevels += 1; } return getSignalStrengthIcon(mContext, level, numLevels, NO_CELL_DATA_TYPE_ICON, @@ -689,6 +689,17 @@ public class InternetDialogController implements AccessPointController.AccessPoi return mergedCarrierEntry != null && mergedCarrierEntry.isDefaultNetwork(); } + int getCarrierNetworkLevel() { + final MergedCarrierEntry mergedCarrierEntry = + mAccessPointController.getMergedCarrierEntry(); + if (mergedCarrierEntry == null) return WifiEntry.WIFI_LEVEL_MIN; + + int level = mergedCarrierEntry.getLevel(); + // To avoid icons not found with WIFI_LEVEL_UNREACHABLE(-1), use WIFI_LEVEL_MIN(0) instead. + if (level < WifiEntry.WIFI_LEVEL_MIN) level = WifiEntry.WIFI_LEVEL_MIN; + return level; + } + @WorkerThread void setMergedCarrierWifiEnabledIfNeed(int subId, boolean enabled) { // If the Carrier Provisions Wi-Fi Merged Networks enabled, do not set the merged carrier diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java index 22a6b0a4bae4..929dda60b1b7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java @@ -157,7 +157,7 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable { @Override public boolean pointInView(float localX, float localY, float slop) { - float top = mClipTopAmount; + float top = Math.max(0, mClipTopAmount); float bottom = mActualHeight; return localX >= -slop && localY >= top - slop && localX < ((mRight - mLeft) + slop) && localY < (bottom + slop); diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt index 0166fa25d526..6a6a65a601fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt @@ -22,6 +22,8 @@ import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastSender +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.wm.shell.TaskView import org.junit.Before import org.junit.Test @@ -45,6 +47,10 @@ class DetailDialogTest : SysuiTestCase() { private lateinit var controlViewHolder: ControlViewHolder @Mock private lateinit var pendingIntent: PendingIntent + @Mock + private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var activityStarter: ActivityStarter @Before fun setUp() { @@ -69,7 +75,9 @@ class DetailDialogTest : SysuiTestCase() { broadcastSender, taskView, pendingIntent, - controlViewHolder + controlViewHolder, + keyguardStateController, + activityStarter ) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt index ceb811b8d8aa..219b3c8561b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaCarouselControllerTest.kt @@ -136,9 +136,18 @@ class MediaCarouselControllerTest : SysuiTestCase() { val resume2 = Triple("resume 2", DATA.copy(active = false, isPlaying = false, - playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true), + playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true), 1000L) + val activeMoreRecent = Triple("active more recent", + DATA.copy(active = false, isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true, lastActive = 2L), + 1000L) + + val activeLessRecent = Triple("active less recent", + DATA.copy(active = false, isPlaying = false, + playbackLocation = MediaData.PLAYBACK_LOCAL, resumption = true, lastActive = 1L), + 1000L) // Expected ordering for media players: // Actively playing local sessions // Actively playing cast sessions diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt index e42ae1c2f878..9a7b129f7597 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataManagerTest.kt @@ -38,6 +38,7 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before +import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -574,6 +575,7 @@ class MediaDataManagerTest : SysuiTestCase() { .onSmartspaceMediaDataLoaded(anyObject(), anyObject(), anyBoolean()) } + @Ignore("b/233283726") @Test fun testOnSmartspaceMediaDataLoaded_hasNoneMediaTarget_callsRemoveListener() { smartspaceMediaDataProvider.onTargetsAvailable(listOf(mediaSmartspaceTarget)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java index 4a8cb0b76dc4..7b1e5c9f7264 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogControllerTest.java @@ -1,9 +1,15 @@ package com.android.systemui.qs.tiles.dialog; import static android.provider.Settings.Global.AIRPLANE_MODE_ON; +import static android.telephony.SignalStrength.NUM_SIGNAL_STRENGTH_BINS; +import static android.telephony.SignalStrength.SIGNAL_STRENGTH_GREAT; +import static android.telephony.SignalStrength.SIGNAL_STRENGTH_POOR; import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_HORIZONTAL_WEIGHT; import static com.android.systemui.qs.tiles.dialog.InternetDialogController.TOAST_PARAMS_VERTICAL_WEIGHT; +import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MAX; +import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_MIN; +import static com.android.wifitrackerlib.WifiEntry.WIFI_LEVEL_UNREACHABLE; import static com.google.common.truth.Truth.assertThat; @@ -17,6 +23,7 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -28,6 +35,7 @@ import android.net.ConnectivityManager; import android.net.wifi.WifiManager; import android.os.Handler; import android.telephony.ServiceState; +import android.telephony.SignalStrength; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.testing.AndroidTestingRunner; @@ -140,6 +148,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { private View mDialogLaunchView; @Mock private WifiStateWorker mWifiStateWorker; + @Mock + private SignalStrength mSignalStrength; private TestableResources mTestableResources; private InternetDialogController mInternetDialogController; @@ -152,6 +162,8 @@ public class InternetDialogControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mTestableResources = mContext.getOrCreateTestableResources(); doReturn(mTelephonyManager).when(mTelephonyManager).createForSubscriptionId(anyInt()); + when(mTelephonyManager.getSignalStrength()).thenReturn(mSignalStrength); + when(mSignalStrength.getLevel()).thenReturn(SIGNAL_STRENGTH_GREAT); when(mKeyguardStateController.isUnlocked()).thenReturn(true); when(mConnectedEntry.isDefaultNetwork()).thenReturn(true); when(mConnectedEntry.hasInternetAccess()).thenReturn(true); @@ -380,7 +392,7 @@ public class InternetDialogControllerTest extends SysuiTestCase { @Test public void getInternetWifiDrawable_withWifiLevelUnreachable_returnNull() { - when(mConnectedEntry.getLevel()).thenReturn(WifiEntry.WIFI_LEVEL_UNREACHABLE); + when(mConnectedEntry.getLevel()).thenReturn(WIFI_LEVEL_UNREACHABLE); Drawable drawable = mInternetDialogController.getInternetWifiDrawable(mConnectedEntry); @@ -638,6 +650,57 @@ public class InternetDialogControllerTest extends SysuiTestCase { assertThat(mInternetDialogController.isWifiScanEnabled()).isTrue(); } + @Test + public void getSignalStrengthDrawableWithLevel_carrierNetworkIsNotActive_useMobileDataLevel() { + // Fake mobile data level as SIGNAL_STRENGTH_POOR(1) + when(mSignalStrength.getLevel()).thenReturn(SIGNAL_STRENGTH_POOR); + // Fake carrier network level as WIFI_LEVEL_MAX(4) + when(mInternetDialogController.getCarrierNetworkLevel()).thenReturn(WIFI_LEVEL_MAX); + + InternetDialogController spyController = spy(mInternetDialogController); + spyController.getSignalStrengthDrawableWithLevel(false /* isCarrierNetworkActive */); + + verify(spyController).getSignalStrengthIcon(any(), eq(SIGNAL_STRENGTH_POOR), + eq(NUM_SIGNAL_STRENGTH_BINS), anyInt(), anyBoolean()); + } + + @Test + public void getSignalStrengthDrawableWithLevel_carrierNetworkIsActive_useCarrierNetworkLevel() { + // Fake mobile data level as SIGNAL_STRENGTH_POOR(1) + when(mSignalStrength.getLevel()).thenReturn(SIGNAL_STRENGTH_POOR); + // Fake carrier network level as WIFI_LEVEL_MAX(4) + when(mInternetDialogController.getCarrierNetworkLevel()).thenReturn(WIFI_LEVEL_MAX); + + InternetDialogController spyController = spy(mInternetDialogController); + spyController.getSignalStrengthDrawableWithLevel(true /* isCarrierNetworkActive */); + + verify(spyController).getSignalStrengthIcon(any(), eq(WIFI_LEVEL_MAX), + eq(WIFI_LEVEL_MAX + 1), anyInt(), anyBoolean()); + } + + @Test + public void getCarrierNetworkLevel_mergedCarrierEntryIsNull_returnMinLevel() { + when(mAccessPointController.getMergedCarrierEntry()).thenReturn(null); + + assertThat(mInternetDialogController.getCarrierNetworkLevel()).isEqualTo(WIFI_LEVEL_MIN); + } + + @Test + public void getCarrierNetworkLevel_getUnreachableLevel_returnMinLevel() { + when(mMergedCarrierEntry.getLevel()).thenReturn(WIFI_LEVEL_UNREACHABLE); + + assertThat(mInternetDialogController.getCarrierNetworkLevel()).isEqualTo(WIFI_LEVEL_MIN); + } + + @Test + public void getCarrierNetworkLevel_getAvailableLevel_returnSameLevel() { + for (int level = WIFI_LEVEL_MIN; level <= WIFI_LEVEL_MAX; level++) { + when(mMergedCarrierEntry.getLevel()).thenReturn(level); + + assertThat(mInternetDialogController.getCarrierNetworkLevel()).isEqualTo(level); + } + } + private String getResourcesString(String name) { return mContext.getResources().getString(getResourcesId(name)); } diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index f26d9f9f49e6..76cac934fdfe 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -24,6 +24,7 @@ import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_INACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_NOT_CONFIGURED; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE; +import static android.telephony.SubscriptionManager.isValidSubscriptionId; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionSnapshot; import static com.android.server.vcn.TelephonySubscriptionTracker.TelephonySubscriptionTrackerCallback; @@ -167,6 +168,10 @@ public class VcnManagementService extends IVcnManagementService.Stub { static final String VCN_CONFIG_FILE = new File(Environment.getDataSystemDirectory(), "vcn/configs.xml").getPath(); + // TODO(b/176956496): Directly use CarrierServiceBindHelper.UNBIND_DELAY_MILLIS + @VisibleForTesting(visibility = Visibility.PRIVATE) + static final long CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS = TimeUnit.SECONDS.toMillis(30); + /* Binder context for this service */ @NonNull private final Context mContext; @NonNull private final Dependencies mDeps; @@ -360,15 +365,12 @@ public class VcnManagementService extends IVcnManagementService.Stub { /** Notifies the VcnManagementService that external dependencies can be set up. */ public void systemReady() { - // Always run on the handler thread to ensure consistency. - mHandler.post(() -> { - mNetworkProvider.register(); - mContext.getSystemService(ConnectivityManager.class) - .registerNetworkCallback( - new NetworkRequest.Builder().clearCapabilities().build(), - mTrackingNetworkCallback); - mTelephonySubscriptionTracker.register(); - }); + mNetworkProvider.register(); + mContext.getSystemService(ConnectivityManager.class) + .registerNetworkCallback( + new NetworkRequest.Builder().clearCapabilities().build(), + mTrackingNetworkCallback); + mTelephonySubscriptionTracker.register(); } private void enforcePrimaryUser() { @@ -509,15 +511,22 @@ public class VcnManagementService extends IVcnManagementService.Stub { if (!mVcns.containsKey(subGrp)) { startVcnLocked(subGrp, entry.getValue()); } + + // Cancel any scheduled teardowns for active subscriptions + mHandler.removeCallbacksAndMessages(mVcns.get(subGrp)); } } - // Schedule teardown of any VCN instances that have lost carrier privileges + // Schedule teardown of any VCN instances that have lost carrier privileges (after a + // delay) for (Entry<ParcelUuid, Vcn> entry : mVcns.entrySet()) { final ParcelUuid subGrp = entry.getKey(); final VcnConfig config = mConfigs.get(subGrp); final boolean isActiveSubGrp = isActiveSubGroup(subGrp, snapshot); + final boolean isValidActiveDataSubIdNotInVcnSubGrp = + isValidSubscriptionId(snapshot.getActiveDataSubscriptionId()) + && !isActiveSubGroup(subGrp, snapshot); // TODO(b/193687515): Support multiple VCNs active at the same time if (config == null @@ -527,12 +536,31 @@ public class VcnManagementService extends IVcnManagementService.Stub { final ParcelUuid uuidToTeardown = subGrp; final Vcn instanceToTeardown = entry.getValue(); - stopVcnLocked(uuidToTeardown); - - // TODO(b/181789060): invoke asynchronously after Vcn notifies - // through VcnCallback - notifyAllPermissionedStatusCallbacksLocked( - uuidToTeardown, VCN_STATUS_CODE_INACTIVE); + // TODO(b/193687515): Support multiple VCNs active at the same time + // If directly switching to a subscription not in the current group, + // teardown immediately to prevent other subscription's network from being + // outscored by the VCN. Otherwise, teardown after a delay to ensure that + // SIM profile switches do not trigger the VCN to cycle. + final long teardownDelayMs = + isValidActiveDataSubIdNotInVcnSubGrp + ? 0 + : CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS; + mHandler.postDelayed(() -> { + synchronized (mLock) { + // Guard against case where this is run after a old instance was + // torn down, and a new instance was started. Verify to ensure + // correct instance is torn down. This could happen as a result of a + // Carrier App manually removing/adding a VcnConfig. + if (mVcns.get(uuidToTeardown) == instanceToTeardown) { + stopVcnLocked(uuidToTeardown); + + // TODO(b/181789060): invoke asynchronously after Vcn notifies + // through VcnCallback + notifyAllPermissionedStatusCallbacksLocked( + uuidToTeardown, VCN_STATUS_CODE_INACTIVE); + } + } + }, instanceToTeardown, teardownDelayMs); } else { // If this VCN's status has not changed, update it with the new snapshot entry.getValue().updateSubscriptionSnapshot(mLastSnapshot); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index 7253b4946661..7fe3c1f5272b 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -2260,7 +2260,8 @@ public final class BatteryStatsService extends IBatteryStats.Stub private void dumpHelp(PrintWriter pw) { pw.println("Battery stats (batterystats) dump options:"); pw.println(" [--checkin] [--proto] [--history] [--history-start] [--charged] [-c]"); - pw.println(" [--daily] [--reset] [--write] [--new-daily] [--read-daily] [-h] [<package.name>]"); + pw.println(" [--daily] [--reset] [--reset-all] [--write] [--new-daily] [--read-daily]"); + pw.println(" [-h] [<package.name>]"); pw.println(" --checkin: generate output for a checkin report; will write (and clear) the"); pw.println(" last old completed stats when they had been reset."); pw.println(" -c: write the current stats in checkin format."); @@ -2271,6 +2272,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub pw.println(" --charged: only output data since last charged."); pw.println(" --daily: only output full daily data."); pw.println(" --reset: reset the stats, clearing all current data."); + pw.println(" --reset-all: reset the stats, clearing all current and past data."); pw.println(" --write: force write current collected stats to disk."); pw.println(" --new-daily: immediately create and write new daily stats record."); pw.println(" --read-daily: read-load last written daily stats."); @@ -2407,6 +2409,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub flags |= BatteryStats.DUMP_CHARGED_ONLY; } else if ("--daily".equals(arg)) { flags |= BatteryStats.DUMP_DAILY_ONLY; + } else if ("--reset-all".equals(arg)) { + awaitCompletion(); + synchronized (mStats) { + mStats.resetAllStatsCmdLocked(); + mBatteryUsageStatsStore.removeAllSnapshots(); + pw.println("Battery stats and history reset."); + noOutput = true; + } } else if ("--reset".equals(arg)) { awaitCompletion(); synchronized (mStats) { diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING index 03eddc9634d7..e4f624d4ac28 100644 --- a/services/core/java/com/android/server/am/TEST_MAPPING +++ b/services/core/java/com/android/server/am/TEST_MAPPING @@ -63,6 +63,32 @@ ] } ], + "presubmit-large": [ + { + "name": "CtsUsageStatsTestCases", + "file_patterns": [ + "ActivityManagerService\\.java", + "BroadcastQueue\\.java" + ], + "options": [ + { + "include-filter": "android.app.usage.cts.BroadcastResponseStatsTest" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + }, + { + "exclude-annotation": "androidx.test.filters.MediumTest" + }, + { + "exclude-annotation": "androidx.test.filters.LargeTest" + } + ] + } + ], "postsubmit": [ { "name": "FrameworksServicesTests", diff --git a/services/core/java/com/android/server/content/ContentService.java b/services/core/java/com/android/server/content/ContentService.java index f93e06d73a10..2564ae8d8783 100644 --- a/services/core/java/com/android/server/content/ContentService.java +++ b/services/core/java/com/android/server/content/ContentService.java @@ -26,6 +26,7 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.ActivityManager.RestrictionLevel; import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -52,6 +53,7 @@ import android.content.pm.ProviderInfo; import android.database.IContentObserver; import android.database.sqlite.SQLiteException; import android.net.Uri; +import android.os.AppBackgroundRestrictionsInfo; import android.os.Binder; import android.os.Build; import android.os.Bundle; @@ -1544,7 +1546,8 @@ public final class ContentService extends IContentService.Stub { } if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND || isUidActive) { FrameworkStatsLog.write(FrameworkStatsLog.SYNC_EXEMPTION_OCCURRED, - callingUid, getProcStateForStatsd(procState), isUidActive); + callingUid, getProcStateForStatsd(procState), isUidActive, + getRestrictionLevelForStatsd(ami.getRestrictionLevel(callingUid))); return ContentResolver.SYNC_EXEMPTION_PROMOTE_BUCKET; } return ContentResolver.SYNC_EXEMPTION_NONE; @@ -1599,6 +1602,27 @@ public final class ContentService extends IContentService.Stub { } } + private int getRestrictionLevelForStatsd(@RestrictionLevel int level) { + switch (level) { + case ActivityManager.RESTRICTION_LEVEL_UNKNOWN: + return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN; + case ActivityManager.RESTRICTION_LEVEL_UNRESTRICTED: + return AppBackgroundRestrictionsInfo.LEVEL_UNRESTRICTED; + case ActivityManager.RESTRICTION_LEVEL_EXEMPTED: + return AppBackgroundRestrictionsInfo.LEVEL_EXEMPTED; + case ActivityManager.RESTRICTION_LEVEL_ADAPTIVE_BUCKET: + return AppBackgroundRestrictionsInfo.LEVEL_ADAPTIVE_BUCKET; + case ActivityManager.RESTRICTION_LEVEL_RESTRICTED_BUCKET: + return AppBackgroundRestrictionsInfo.LEVEL_RESTRICTED_BUCKET; + case ActivityManager.RESTRICTION_LEVEL_BACKGROUND_RESTRICTED: + return AppBackgroundRestrictionsInfo.LEVEL_BACKGROUND_RESTRICTED; + case ActivityManager.RESTRICTION_LEVEL_HIBERNATION: + return AppBackgroundRestrictionsInfo.LEVEL_HIBERNATION; + default: + return AppBackgroundRestrictionsInfo.LEVEL_UNKNOWN; + } + } + /** {@hide} */ @VisibleForTesting public static final class ObserverNode { diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index b624d438a4f7..72612a0468cd 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -146,6 +146,7 @@ import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.OptionalInt; +import java.util.function.Consumer; /** The system implementation of {@link IInputManager} that manages input devices. */ public class InputManagerService extends IInputManager.Stub @@ -168,6 +169,8 @@ public class InputManagerService extends IInputManager.Stub private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 7; private static final int DEFAULT_VIBRATION_MAGNITUDE = 192; + private static final AdditionalDisplayInputProperties + DEFAULT_ADDITIONAL_DISPLAY_INPUT_PROPERTIES = new AdditionalDisplayInputProperties(); /** * We know the issue and are working to fix it, so suppressing the toast to not annoy @@ -281,6 +284,7 @@ public class InputManagerService extends IInputManager.Stub // Guards per-display input properties and properties relating to the mouse pointer. // Threads can wait on this lock to be notified the next time the display on which the mouse // pointer is shown has changed. + // WARNING: Do not call other services outside of input while holding this lock. private final Object mAdditionalDisplayInputPropertiesLock = new Object(); // Forces the PointerController to target a specific display id. @@ -299,6 +303,11 @@ public class InputManagerService extends IInputManager.Stub @GuardedBy("mAdditionalDisplayInputPropertiesLock") private final SparseArray<AdditionalDisplayInputProperties> mAdditionalDisplayInputProperties = new SparseArray<>(); + // This contains the per-display properties that are currently applied by native code. It should + // be kept in sync with the properties for mRequestedPointerDisplayId. + @GuardedBy("mAdditionalDisplayInputPropertiesLock") + private final AdditionalDisplayInputProperties mCurrentDisplayProperties = + new AdditionalDisplayInputProperties(); @GuardedBy("mAdditionalDisplayInputPropertiesLock") private int mIconType = PointerIcon.TYPE_NOT_SPECIFIED; @GuardedBy("mAdditionalDisplayInputPropertiesLock") @@ -571,27 +580,19 @@ public class InputManagerService extends IInputManager.Stub } private void setDisplayViewportsInternal(List<DisplayViewport> viewports) { - synchronized (mAdditionalDisplayInputPropertiesLock) { - final DisplayViewport[] vArray = new DisplayViewport[viewports.size()]; - for (int i = viewports.size() - 1; i >= 0; --i) { - vArray[i] = viewports.get(i); - } - mNative.setDisplayViewports(vArray); - // Always attempt to update the pointer display when viewports change. - updatePointerDisplayId(); + final DisplayViewport[] vArray = new DisplayViewport[viewports.size()]; + for (int i = viewports.size() - 1; i >= 0; --i) { + vArray[i] = viewports.get(i); + } + mNative.setDisplayViewports(vArray); - if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { - final AdditionalDisplayInputProperties properties = - mAdditionalDisplayInputProperties.get(mOverriddenPointerDisplayId); - if (properties != null) { - updatePointerIconVisibleLocked(properties.pointerIconVisible); - updatePointerAccelerationLocked(properties.pointerAcceleration); - return; - } + // Attempt to update the pointer display when viewports change when there is no override. + // Take care to not make calls to window manager while holding internal locks. + final int pointerDisplayId = mWindowManagerCallbacks.getPointerDisplayId(); + synchronized (mAdditionalDisplayInputPropertiesLock) { + if (mOverriddenPointerDisplayId == Display.INVALID_DISPLAY) { + updatePointerDisplayIdLocked(pointerDisplayId); } - updatePointerIconVisibleLocked( - AdditionalDisplayInputProperties.DEFAULT_POINTER_ICON_VISIBLE); - updatePointerAccelerationLocked(IInputConstants.DEFAULT_POINTER_ACCELERATION); } } @@ -1743,12 +1744,7 @@ public class InputManagerService extends IInputManager.Stub mPointerIconDisplayContext = null; } - synchronized (mAdditionalDisplayInputPropertiesLock) { - setPointerIconVisible(AdditionalDisplayInputProperties.DEFAULT_POINTER_ICON_VISIBLE, - displayId); - setPointerAcceleration(AdditionalDisplayInputProperties.DEFAULT_POINTER_ACCELERATION, - displayId); - } + updateAdditionalDisplayInputProperties(displayId, AdditionalDisplayInputProperties::reset); mNative.displayRemoved(displayId); } @@ -1835,57 +1831,13 @@ public class InputManagerService extends IInputManager.Stub } private void setPointerAcceleration(float acceleration, int displayId) { - synchronized (mAdditionalDisplayInputPropertiesLock) { - AdditionalDisplayInputProperties properties = - mAdditionalDisplayInputProperties.get(displayId); - if (properties == null) { - properties = new AdditionalDisplayInputProperties(); - mAdditionalDisplayInputProperties.put(displayId, properties); - } - properties.pointerAcceleration = acceleration; - if (properties.allDefaults()) { - mAdditionalDisplayInputProperties.remove(displayId); - } - if (mOverriddenPointerDisplayId == displayId) { - updatePointerAccelerationLocked(acceleration); - } - } - } - - @GuardedBy("mAdditionalDisplayInputPropertiesLock") - private void updatePointerAccelerationLocked(float acceleration) { - mNative.setPointerAcceleration(acceleration); + updateAdditionalDisplayInputProperties(displayId, + properties -> properties.pointerAcceleration = acceleration); } private void setPointerIconVisible(boolean visible, int displayId) { - synchronized (mAdditionalDisplayInputPropertiesLock) { - AdditionalDisplayInputProperties properties = - mAdditionalDisplayInputProperties.get(displayId); - if (properties == null) { - properties = new AdditionalDisplayInputProperties(); - mAdditionalDisplayInputProperties.put(displayId, properties); - } - properties.pointerIconVisible = visible; - if (properties.allDefaults()) { - mAdditionalDisplayInputProperties.remove(displayId); - } - if (mOverriddenPointerDisplayId == displayId) { - updatePointerIconVisibleLocked(visible); - } - } - } - - @GuardedBy("mAdditionalDisplayInputPropertiesLock") - private void updatePointerIconVisibleLocked(boolean visible) { - if (visible) { - if (mIconType == PointerIcon.TYPE_CUSTOM) { - mNative.setCustomPointerIcon(mIcon); - } else { - mNative.setPointerIconType(mIconType); - } - } else { - mNative.setPointerIconType(PointerIcon.TYPE_NULL); - } + updateAdditionalDisplayInputProperties(displayId, + properties -> properties.pointerIconVisible = visible); } private void registerPointerSpeedSettingObserver() { @@ -2023,22 +1975,18 @@ public class InputManagerService extends IInputManager.Stub /** * Update the display on which the mouse pointer is shown. - * If there is an overridden display for the mouse pointer, use that. Otherwise, query - * WindowManager for the pointer display. * * @return true if the pointer displayId changed, false otherwise. */ - private boolean updatePointerDisplayId() { - synchronized (mAdditionalDisplayInputPropertiesLock) { - final int pointerDisplayId = mOverriddenPointerDisplayId != Display.INVALID_DISPLAY - ? mOverriddenPointerDisplayId : mWindowManagerCallbacks.getPointerDisplayId(); - if (mRequestedPointerDisplayId == pointerDisplayId) { - return false; - } - mRequestedPointerDisplayId = pointerDisplayId; - mNative.setPointerDisplayId(pointerDisplayId); - return true; + @GuardedBy("mAdditionalDisplayInputPropertiesLock") + private boolean updatePointerDisplayIdLocked(int pointerDisplayId) { + if (mRequestedPointerDisplayId == pointerDisplayId) { + return false; } + mRequestedPointerDisplayId = pointerDisplayId; + mNative.setPointerDisplayId(pointerDisplayId); + applyAdditionalDisplayInputProperties(); + return true; } private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) { @@ -2051,25 +1999,23 @@ public class InputManagerService extends IInputManager.Stub args.mPointerDisplayId, args.mXPosition, args.mYPosition); } - private boolean setVirtualMousePointerDisplayIdBlocking(int displayId) { - // Indicates whether this request is for removing the override. - final boolean removingOverride = displayId == Display.INVALID_DISPLAY; + private boolean setVirtualMousePointerDisplayIdBlocking(int overrideDisplayId) { + final boolean isRemovingOverride = overrideDisplayId == Display.INVALID_DISPLAY; + + // Take care to not make calls to window manager while holding internal locks. + final int resolvedDisplayId = isRemovingOverride + ? mWindowManagerCallbacks.getPointerDisplayId() + : overrideDisplayId; synchronized (mAdditionalDisplayInputPropertiesLock) { - mOverriddenPointerDisplayId = displayId; - if (!removingOverride) { - final AdditionalDisplayInputProperties properties = - mAdditionalDisplayInputProperties.get(displayId); - if (properties != null) { - updatePointerAccelerationLocked(properties.pointerAcceleration); - updatePointerIconVisibleLocked(properties.pointerIconVisible); - } - } - if (!updatePointerDisplayId() && mAcknowledgedPointerDisplayId == displayId) { + mOverriddenPointerDisplayId = overrideDisplayId; + + if (!updatePointerDisplayIdLocked(resolvedDisplayId) + && mAcknowledgedPointerDisplayId == resolvedDisplayId) { // The requested pointer display is already set. return true; } - if (removingOverride && mAcknowledgedPointerDisplayId == Display.INVALID_DISPLAY) { + if (isRemovingOverride && mAcknowledgedPointerDisplayId == Display.INVALID_DISPLAY) { // The pointer display override is being removed, but the current pointer display // is already invalid. This can happen when the PointerController is destroyed as a // result of the removal of all input devices that can control the pointer. @@ -2087,7 +2033,7 @@ public class InputManagerService extends IInputManager.Stub // reported new displayId is the one we requested. This check ensures that if two // competing overrides are requested in succession, the caller can be notified if one // of them fails. - return removingOverride || mAcknowledgedPointerDisplayId == displayId; + return isRemovingOverride || mAcknowledgedPointerDisplayId == overrideDisplayId; } } @@ -2393,15 +2339,10 @@ public class InputManagerService extends IInputManager.Stub synchronized (mAdditionalDisplayInputPropertiesLock) { mIcon = null; mIconType = iconType; - if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { - final AdditionalDisplayInputProperties properties = - mAdditionalDisplayInputProperties.get(mOverriddenPointerDisplayId); - if (properties == null || properties.pointerIconVisible) { - mNative.setPointerIconType(mIconType); - } - } else { - mNative.setPointerIconType(mIconType); - } + + if (!mCurrentDisplayProperties.pointerIconVisible) return; + + mNative.setPointerIconType(mIconType); } } @@ -2412,17 +2353,10 @@ public class InputManagerService extends IInputManager.Stub synchronized (mAdditionalDisplayInputPropertiesLock) { mIconType = PointerIcon.TYPE_CUSTOM; mIcon = icon; - if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { - final AdditionalDisplayInputProperties properties = - mAdditionalDisplayInputProperties.get(mOverriddenPointerDisplayId); - if (properties == null || properties.pointerIconVisible) { - // Only set the icon if it is not currently hidden; otherwise, it will be set - // once it's no longer hidden. - mNative.setCustomPointerIcon(mIcon); - } - } else { - mNative.setCustomPointerIcon(mIcon); - } + + if (!mCurrentDisplayProperties.pointerIconVisible) return; + + mNative.setCustomPointerIcon(mIcon); } } @@ -3852,14 +3786,79 @@ public class InputManagerService extends IInputManager.Stub (float) IInputConstants.DEFAULT_POINTER_ACCELERATION; // The pointer acceleration for this display. - public float pointerAcceleration = DEFAULT_POINTER_ACCELERATION; + public float pointerAcceleration; // Whether the pointer icon should be visible or hidden on this display. - public boolean pointerIconVisible = DEFAULT_POINTER_ICON_VISIBLE; + public boolean pointerIconVisible; + + AdditionalDisplayInputProperties() { + reset(); + } public boolean allDefaults() { return Float.compare(pointerAcceleration, DEFAULT_POINTER_ACCELERATION) == 0 && pointerIconVisible == DEFAULT_POINTER_ICON_VISIBLE; } + + public void reset() { + pointerAcceleration = DEFAULT_POINTER_ACCELERATION; + pointerIconVisible = DEFAULT_POINTER_ICON_VISIBLE; + } + } + + private void applyAdditionalDisplayInputProperties() { + synchronized (mAdditionalDisplayInputPropertiesLock) { + AdditionalDisplayInputProperties properties = + mAdditionalDisplayInputProperties.get(mRequestedPointerDisplayId); + if (properties == null) properties = DEFAULT_ADDITIONAL_DISPLAY_INPUT_PROPERTIES; + applyAdditionalDisplayInputPropertiesLocked(properties); + } + } + + @GuardedBy("mAdditionalDisplayInputPropertiesLock") + private void applyAdditionalDisplayInputPropertiesLocked( + AdditionalDisplayInputProperties properties) { + // Handle changes to each of the individual properties. + if (properties.pointerIconVisible != mCurrentDisplayProperties.pointerIconVisible) { + mCurrentDisplayProperties.pointerIconVisible = properties.pointerIconVisible; + if (properties.pointerIconVisible) { + if (mIconType == PointerIcon.TYPE_CUSTOM) { + Objects.requireNonNull(mIcon); + mNative.setCustomPointerIcon(mIcon); + } else { + mNative.setPointerIconType(mIconType); + } + } else { + mNative.setPointerIconType(PointerIcon.TYPE_NULL); + } + } + + if (properties.pointerAcceleration != mCurrentDisplayProperties.pointerAcceleration) { + mCurrentDisplayProperties.pointerAcceleration = properties.pointerAcceleration; + mNative.setPointerAcceleration(properties.pointerAcceleration); + } + } + + private void updateAdditionalDisplayInputProperties(int displayId, + Consumer<AdditionalDisplayInputProperties> updater) { + synchronized (mAdditionalDisplayInputPropertiesLock) { + AdditionalDisplayInputProperties properties = + mAdditionalDisplayInputProperties.get(displayId); + if (properties == null) { + properties = new AdditionalDisplayInputProperties(); + mAdditionalDisplayInputProperties.put(displayId, properties); + } + updater.accept(properties); + if (properties.allDefaults()) { + mAdditionalDisplayInputProperties.remove(displayId); + } + if (displayId != mRequestedPointerDisplayId) { + Log.i(TAG, "Not applying additional properties for display " + displayId + + " because the pointer is currently targeting display " + + mRequestedPointerDisplayId + "."); + return; + } + applyAdditionalDisplayInputPropertiesLocked(properties); + } } } diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java index 31d5136c80a5..e5eed9928411 100644 --- a/services/core/java/com/android/server/location/LocationManagerService.java +++ b/services/core/java/com/android/server/location/LocationManagerService.java @@ -17,6 +17,7 @@ package com.android.server.location; import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.INTERACT_ACROSS_USERS; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; import static android.app.compat.CompatChanges.isChangeEnabled; import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE; @@ -1030,8 +1031,10 @@ public class LocationManagerService extends ILocationManager.Stub implements @Override public void addProviderRequestListener(IProviderRequestListener listener) { - for (LocationProviderManager manager : mProviderManagers) { - manager.addProviderRequestListener(listener); + if (mContext.checkCallingOrSelfPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED) { + for (LocationProviderManager manager : mProviderManagers) { + manager.addProviderRequestListener(listener); + } } } diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java index 1937852fa333..098e8f74749c 100644 --- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java +++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java @@ -45,14 +45,15 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.ArrayMap; import android.util.Slog; +import android.view.ContentRecordingSession; import android.window.WindowContainerToken; import com.android.internal.util.ArrayUtils; import com.android.internal.util.DumpUtils; import com.android.server.LocalServices; import com.android.server.SystemService; -import com.android.server.SystemService.TargetUser; import com.android.server.Watchdog; +import com.android.server.wm.WindowManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -381,6 +382,27 @@ public final class MediaProjectionManagerService extends SystemService } } + /** + * Updates the current content mirroring session. + */ + @Override + public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession, + @NonNull IMediaProjection projection) { + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + if (!isValidMediaProjection(projection)) { + throw new SecurityException("Invalid media projection"); + } + LocalServices.getService( + WindowManagerInternal.class).setContentRecordingSession( + incomingSession); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + @Override // Binder call public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java index 8552bba5d608..d34682df3413 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -2516,7 +2516,6 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt final int[] userIds = filterUserId == UserHandle.USER_ALL ? getAllUserIds() : new int[] { filterUserId }; - boolean installPermissionsChanged = false; boolean runtimePermissionsRevoked = false; int[] updatedUserIds = EMPTY_INT_ARRAY; @@ -2635,7 +2634,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt UidPermissionState origState = uidState; - boolean installPermissionsChangedForUser = false; + boolean changedInstallPermission = false; if (replace) { userState.setInstallPermissionsFixed(ps.getPackageName(), false); @@ -2801,7 +2800,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt && origState.isPermissionGranted(permName))))) { // Grant an install permission. if (uidState.grantPermission(bp)) { - installPermissionsChangedForUser = true; + changedInstallPermission = true; } } else if (bp.isRuntime()) { boolean hardRestricted = bp.isHardRestricted(); @@ -2941,12 +2940,12 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } } if (uidState.removePermissionState(bp.getName())) { - installPermissionsChangedForUser = true; + changedInstallPermission = true; } } } - if ((installPermissionsChangedForUser || replace) + if ((changedInstallPermission || replace) && !userState.areInstallPermissionsFixed(ps.getPackageName()) && !ps.isSystem() || ps.getTransientState().isUpdatedSystemApp()) { // This is the first that we have heard about this package, so the @@ -2955,12 +2954,6 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt userState.setInstallPermissionsFixed(ps.getPackageName(), true); } - if (installPermissionsChangedForUser) { - installPermissionsChanged = true; - if (replace) { - updatedUserIds = ArrayUtils.appendInt(updatedUserIds, userId); - } - } updatedUserIds = revokePermissionsNoLongerImplicitLocked(uidState, pkg.getPackageName(), uidImplicitPermissions, uidTargetSdkVersion, userId, updatedUserIds); @@ -2977,12 +2970,8 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt // Persist the runtime permissions state for users with changes. If permissions // were revoked because no app in the shared user declares them we have to // write synchronously to avoid losing runtime permissions state. - // Also write synchronously if we changed any install permission for an updated app, because - // the install permission state is likely already fixed before update, and if we lose the - // changes here the app won't be reconsidered for newly-added install permissions. if (callback != null) { - callback.onPermissionUpdated(updatedUserIds, - (replace && installPermissionsChanged) || runtimePermissionsRevoked); + callback.onPermissionUpdated(updatedUserIds, runtimePermissionsRevoked); } for (int userId : updatedUserIds) { diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java index 87523f44d4b6..5d2d5826f227 100644 --- a/services/core/java/com/android/server/wm/ContentRecorder.java +++ b/services/core/java/com/android/server/wm/ContentRecorder.java @@ -207,7 +207,8 @@ final class ContentRecorder { // Update the cached session state first, since updating the service will result in always // returning to this instance to update recording state. mContentRecordingSession = null; - mDisplayContent.mWmService.setContentRecordingSession(null); + mDisplayContent.mWmService.mContentRecordingController.setContentRecordingSessionLocked( + null, mDisplayContent.mWmService); } /** diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index c0d7d1362ac3..9eee7ba871a2 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -30,6 +30,7 @@ import android.hardware.display.DisplayManagerInternal; import android.os.Bundle; import android.os.IBinder; import android.util.Pair; +import android.view.ContentRecordingSession; import android.view.Display; import android.view.IInputFilter; import android.view.IRemoteAnimationFinishedCallback; @@ -869,4 +870,16 @@ public abstract class WindowManagerInternal { */ public abstract boolean isPointInsideWindow( @NonNull IBinder windowToken, int displayId, float displayX, float displayY); + + /** + * Updates the content recording session. If a different session is already in progress, then + * the pre-existing session is stopped, and the new incoming session takes over. + * + * The DisplayContent for the new session will begin recording when + * {@link RootWindowContainer#onDisplayChanged} is invoked for the new {@link VirtualDisplay}. + * Must be invoked for a valid MediaProjection session. + * + * @param incomingSession the nullable incoming content recording session + */ + public abstract void setContentRecordingSession(ContentRecordingSession incomingSession); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7a5480401de8..902218621cd0 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -2956,16 +2956,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - /** - * Updates the current content mirroring session. - */ - @Override - public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) { - synchronized (mGlobalLock) { - mContentRecordingController.setContentRecordingSessionLocked(incomingSession, this); - } - } - // TODO(multi-display): remove when no default display use case. void prepareAppTransitionNone() { if (!checkCallingPermission(MANAGE_APP_TOKENS, "prepareAppTransition()")) { @@ -8242,6 +8232,14 @@ public class WindowManagerService extends IWindowManager.Stub return w.getBounds().contains((int) displayX, (int) displayY); } } + + @Override + public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) { + synchronized (mGlobalLock) { + mContentRecordingController.setContentRecordingSessionLocked(incomingSession, + WindowManagerService.this); + } + } } void registerAppFreezeListener(AppFreezeListener listener) { diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt index e78f0c77d6b3..844f5d4cd3eb 100644 --- a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt +++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt @@ -36,6 +36,7 @@ import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.`when` +import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.doAnswer import org.mockito.Mockito.never import org.mockito.Mockito.spy @@ -226,7 +227,8 @@ class InputManagerServiceTests { @Test fun onDisplayRemoved_resetAllAdditionalInputProperties() { - localService.setVirtualMousePointerDisplayId(10) + setVirtualMousePointerDisplayIdAndVerify(10) + localService.setPointerIconVisible(false, 10) verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) localService.setPointerAcceleration(5f, 10) @@ -237,9 +239,66 @@ class InputManagerServiceTests { verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED)) verify(native).setPointerAcceleration( eq(IInputConstants.DEFAULT_POINTER_ACCELERATION.toFloat())) + verifyNoMoreInteractions(native) + // This call should not block because the virtual mouse pointer override was never removed. localService.setVirtualMousePointerDisplayId(10) + verify(native).setPointerDisplayId(eq(10)) verifyNoMoreInteractions(native) } + + @Test + fun updateAdditionalInputPropertiesForOverrideDisplay() { + setVirtualMousePointerDisplayIdAndVerify(10) + + localService.setPointerIconVisible(false, 10) + verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) + localService.setPointerAcceleration(5f, 10) + verify(native).setPointerAcceleration(eq(5f)) + + localService.setPointerIconVisible(true, 10) + verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED)) + localService.setPointerAcceleration(1f, 10) + verify(native).setPointerAcceleration(eq(1f)) + + // Verify that setting properties on a different display is not propagated until the + // pointer is moved to that display. + localService.setPointerIconVisible(false, 20) + localService.setPointerAcceleration(6f, 20) + verifyNoMoreInteractions(native) + + clearInvocations(native) + setVirtualMousePointerDisplayIdAndVerify(20) + + verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) + verify(native).setPointerAcceleration(eq(6f)) + } + + @Test + fun setAdditionalInputPropertiesBeforeOverride() { + localService.setPointerIconVisible(false, 10) + localService.setPointerAcceleration(5f, 10) + + verifyNoMoreInteractions(native) + + setVirtualMousePointerDisplayIdAndVerify(10) + + verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) + verify(native).setPointerAcceleration(eq(5f)) + } + + private fun setVirtualMousePointerDisplayIdAndVerify(overrideDisplayId: Int) { + val thread = Thread { localService.setVirtualMousePointerDisplayId(overrideDisplayId) } + thread.start() + + // Allow some time for the set override call to park while waiting for the native callback. + Thread.sleep(100 /*millis*/) + verify(native).setPointerDisplayId(overrideDisplayId) + + service.onPointerDisplayIdChanged(overrideDisplayId, 0f, 0f) + testLooper.dispatchNext() + verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, 0f, 0f) + thread.join(100 /*millis*/) + } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index c5f785ea7680..32f3bfe5166c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -2441,7 +2441,7 @@ public class DisplayContentTests extends WindowTestsBase { ContentRecordingSession session = ContentRecordingSession.createDisplaySession( tokenToMirror); session.setDisplayId(displayId); - mWm.setContentRecordingSession(session); + mWm.mContentRecordingController.setContentRecordingSessionLocked(session, mWm); actualDC.updateRecording(); // THEN mirroring is not started, since a null surface indicates the VirtualDisplay is off. @@ -2470,7 +2470,7 @@ public class DisplayContentTests extends WindowTestsBase { ContentRecordingSession session = ContentRecordingSession.createDisplaySession( tokenToMirror); session.setDisplayId(displayId); - mWm.setContentRecordingSession(session); + mWm.mContentRecordingController.setContentRecordingSessionLocked(session, mWm); mWm.mRoot.onDisplayAdded(displayId); // WHEN getting the DisplayContent for the new virtual display. diff --git a/services/usage/java/com/android/server/usage/TEST_MAPPING b/services/usage/java/com/android/server/usage/TEST_MAPPING index 523d5f95c6f3..1c0c71b65fd7 100644 --- a/services/usage/java/com/android/server/usage/TEST_MAPPING +++ b/services/usage/java/com/android/server/usage/TEST_MAPPING @@ -20,6 +20,28 @@ ] } ], + "presubmit-large": [ + { + "name": "CtsUsageStatsTestCases", + "options": [ + { + "include-filter": "android.app.usage.cts.BroadcastResponseStatsTest" + }, + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "exclude-annotation": "android.platform.test.annotations.FlakyTest" + }, + { + "exclude-annotation": "androidx.test.filters.MediumTest" + }, + { + "exclude-annotation": "androidx.test.filters.LargeTest" + } + ] + } + ], "postsubmit": [ { "name": "CtsUsageStatsTestCases", diff --git a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java index dc96c66bff29..df9fc6601ac5 100644 --- a/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java +++ b/services/usb/java/com/android/server/usb/UsbDirectMidiDevice.java @@ -80,9 +80,13 @@ public final class UsbDirectMidiDevice implements Closeable { // of cycles and not being permanently stuck. private static final int BULK_TRANSFER_TIMEOUT_MILLISECONDS = 10; + // Arbitrary number for timeout when closing a thread + private static final int THREAD_JOIN_TIMEOUT_MILLISECONDS = 50; + private ArrayList<UsbDeviceConnection> mUsbDeviceConnections; private ArrayList<ArrayList<UsbEndpoint>> mInputUsbEndpoints; private ArrayList<ArrayList<UsbEndpoint>> mOutputUsbEndpoints; + private ArrayList<Thread> mThreads; private UsbMidiBlockParser mMidiBlockParser = new UsbMidiBlockParser(); private int mDefaultMidiProtocol = MidiDeviceInfo.PROTOCOL_UMP_MIDI_1_0_UP_TO_64_BITS; @@ -273,9 +277,10 @@ public final class UsbDirectMidiDevice implements Closeable { // to USB MIDI for each USB output. mUsbMidiPacketConverter = new UsbMidiPacketConverter(mNumOutputs); - mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>(mUsbInterfaces.size()); - mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(mUsbInterfaces.size()); - mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(mUsbInterfaces.size()); + mUsbDeviceConnections = new ArrayList<UsbDeviceConnection>(); + mInputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(); + mOutputUsbEndpoints = new ArrayList<ArrayList<UsbEndpoint>>(); + mThreads = new ArrayList<Thread>(); for (int interfaceIndex = 0; interfaceIndex < mUsbInterfaces.size(); interfaceIndex++) { ArrayList<UsbEndpoint> inputEndpoints = new ArrayList<UsbEndpoint>(); @@ -327,7 +332,7 @@ public final class UsbDirectMidiDevice implements Closeable { mInputUsbEndpoints.get(connectionIndex).get(endpointIndex); final int portFinal = portNumber; - new Thread("UsbDirectMidiDevice input thread " + portFinal) { + Thread newThread = new Thread("UsbDirectMidiDevice input thread " + portFinal) { @Override public void run() { final UsbRequest request = new UsbRequest(); @@ -335,9 +340,12 @@ public final class UsbDirectMidiDevice implements Closeable { request.initialize(connectionFinal, endpointFinal); byte[] inputBuffer = new byte[endpointFinal.getMaxPacketSize()]; while (true) { + if (Thread.currentThread().interrupted()) { + Log.w(TAG, "input thread interrupted"); + break; + } // Record time of event immediately after waking. long timestamp = System.nanoTime(); - if (!mIsOpen) break; final ByteBuffer byteBuffer = ByteBuffer.wrap(inputBuffer); if (!request.queue(byteBuffer)) { Log.w(TAG, "Cannot queue request"); @@ -382,8 +390,9 @@ public final class UsbDirectMidiDevice implements Closeable { } Log.d(TAG, "input thread exit"); } - }.start(); - + }; + newThread.start(); + mThreads.add(newThread); portNumber++; } } @@ -402,18 +411,23 @@ public final class UsbDirectMidiDevice implements Closeable { final int portFinal = portNumber; final MidiEventScheduler eventSchedulerFinal = mEventSchedulers[portFinal]; - new Thread("UsbDirectMidiDevice output thread " + portFinal) { + Thread newThread = new Thread("UsbDirectMidiDevice output thread " + portFinal) { @Override public void run() { while (true) { + if (Thread.currentThread().interrupted()) { + Log.w(TAG, "output thread interrupted"); + break; + } MidiEvent event; try { event = (MidiEvent) eventSchedulerFinal.waitNextEvent(); } catch (InterruptedException e) { - // try again - continue; + Log.w(TAG, "event scheduler interrupted"); + break; } if (event == null) { + Log.w(TAG, "event is null"); break; } @@ -446,8 +460,9 @@ public final class UsbDirectMidiDevice implements Closeable { } Log.d(TAG, "output thread exit"); } - }.start(); - + }; + newThread.start(); + mThreads.add(newThread); portNumber++; } } @@ -523,6 +538,27 @@ public final class UsbDirectMidiDevice implements Closeable { private void closeLocked() { Log.d(TAG, "closeLocked()"); + + // Send an interrupt signal to threads. + for (Thread thread : mThreads) { + if (thread != null) { + thread.interrupt(); + } + } + + // Wait for threads to actually stop. + for (Thread thread : mThreads) { + if (thread != null) { + try { + thread.join(THREAD_JOIN_TIMEOUT_MILLISECONDS); + } catch (InterruptedException e) { + Log.w(TAG, "thread join interrupted"); + break; + } + } + } + mThreads = null; + for (int i = 0; i < mEventSchedulers.length; i++) { mMidiInputPortReceivers[i].setReceiver(null); mEventSchedulers[i].close(); diff --git a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java index 54b3c400af4f..f924b2e9b932 100644 --- a/tests/vcn/java/com/android/server/VcnManagementServiceTest.java +++ b/tests/vcn/java/com/android/server/VcnManagementServiceTest.java @@ -23,6 +23,7 @@ import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_ACTIVE; import static android.net.vcn.VcnManager.VCN_STATUS_CODE_SAFE_MODE; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS; import static android.telephony.TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS; @@ -276,7 +277,6 @@ public class VcnManagementServiceTest { @Test public void testSystemReady() throws Exception { mVcnMgmtSvc.systemReady(); - mTestLooper.dispatchAll(); verify(mConnMgr).registerNetworkProvider(any(VcnNetworkProvider.class)); verify(mSubscriptionTracker).register(); @@ -494,8 +494,10 @@ public class VcnManagementServiceTest { mVcnMgmtSvc.addVcnUnderlyingNetworkPolicyListener(mMockPolicyListener); triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet()); - mTestLooper.dispatchAll(); + // Verify teardown after delay + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); verify(vcn).teardownAsynchronously(); verify(mMockPolicyListener).onPolicyChanged(); } @@ -521,6 +523,92 @@ public class VcnManagementServiceTest { assertEquals(0, mVcnMgmtSvc.getAllVcns().size()); } + /** + * Tests an intermediate state where carrier privileges are marked as lost before active data + * subId changes during a SIM ejection. + * + * <p>The expected outcome is that the VCN is torn down after a delay, as opposed to + * immediately. + */ + @Test + public void testTelephonyNetworkTrackerCallbackLostCarrierPrivilegesBeforeActiveDataSubChanges() + throws Exception { + setupActiveSubscription(TEST_UUID_2); + + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); + + // Simulate privileges lost + triggerSubscriptionTrackerCbAndGetSnapshot( + TEST_SUBSCRIPTION_ID, + TEST_UUID_2, + Collections.emptySet(), + Collections.emptyMap(), + false /* hasCarrierPrivileges */); + + // Verify teardown after delay + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + verify(vcn).teardownAsynchronously(); + } + + @Test + public void testTelephonyNetworkTrackerCallbackSimSwitchesDoNotKillVcnInstances() + throws Exception { + setupActiveSubscription(TEST_UUID_2); + + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + final Vcn vcn = startAndGetVcnInstance(TEST_UUID_2); + + // Simulate SIM unloaded + triggerSubscriptionTrackerCbAndGetSnapshot( + INVALID_SUBSCRIPTION_ID, + null /* activeDataSubscriptionGroup */, + Collections.emptySet(), + Collections.emptyMap(), + false /* hasCarrierPrivileges */); + + // Simulate new SIM loaded right during teardown delay. + mTestLooper.moveTimeForward( + VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); + mTestLooper.dispatchAll(); + triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2)); + + // Verify that even after the full timeout duration, the VCN instance is not torn down + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + verify(vcn, never()).teardownAsynchronously(); + } + + @Test + public void testTelephonyNetworkTrackerCallbackDoesNotKillNewVcnInstances() throws Exception { + setupActiveSubscription(TEST_UUID_2); + + final TelephonySubscriptionTrackerCallback cb = getTelephonySubscriptionTrackerCallback(); + final Vcn oldInstance = startAndGetVcnInstance(TEST_UUID_2); + + // Simulate SIM unloaded + triggerSubscriptionTrackerCbAndGetSnapshot(null, Collections.emptySet()); + + // Config cleared, SIM reloaded & config re-added right before teardown delay, staring new + // vcnInstance. + mTestLooper.moveTimeForward( + VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS / 2); + mTestLooper.dispatchAll(); + mVcnMgmtSvc.clearVcnConfig(TEST_UUID_2, TEST_PACKAGE_NAME); + triggerSubscriptionTrackerCbAndGetSnapshot(TEST_UUID_2, Collections.singleton(TEST_UUID_2)); + final Vcn newInstance = startAndGetVcnInstance(TEST_UUID_2); + + // Verify that new instance was different, and the old one was torn down + assertTrue(oldInstance != newInstance); + verify(oldInstance).teardownAsynchronously(); + + // Verify that even after the full timeout duration, the new VCN instance is not torn down + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); + mTestLooper.dispatchAll(); + verify(newInstance, never()).teardownAsynchronously(); + } + @Test public void testPackageChangeListenerRegistered() throws Exception { verify(mMockContext).registerReceiver(any(BroadcastReceiver.class), argThat(filter -> { @@ -910,8 +998,6 @@ public class VcnManagementServiceTest { private void setupSubscriptionAndStartVcn( int subId, ParcelUuid subGrp, boolean isVcnActive, boolean hasCarrierPrivileges) { mVcnMgmtSvc.systemReady(); - mTestLooper.dispatchAll(); - triggerSubscriptionTrackerCbAndGetSnapshot( subGrp, Collections.singleton(subGrp), @@ -1007,7 +1093,6 @@ public class VcnManagementServiceTest { private void setupTrackedCarrierWifiNetwork(NetworkCapabilities caps) { mVcnMgmtSvc.systemReady(); - mTestLooper.dispatchAll(); final ArgumentCaptor<NetworkCallback> captor = ArgumentCaptor.forClass(NetworkCallback.class); @@ -1252,14 +1337,15 @@ public class VcnManagementServiceTest { true /* isActive */, true /* hasCarrierPrivileges */); - // VCN is currently active. Lose carrier privileges for TEST_PACKAGE so the VCN goes - // inactive. + // VCN is currently active. Lose carrier privileges for TEST_PACKAGE and hit teardown + // timeout so the VCN goes inactive. final TelephonySubscriptionSnapshot snapshot = triggerSubscriptionTrackerCbAndGetSnapshot( TEST_UUID_1, Collections.singleton(TEST_UUID_1), Collections.singletonMap(TEST_SUBSCRIPTION_ID, TEST_UUID_1), false /* hasCarrierPrivileges */); + mTestLooper.moveTimeForward(VcnManagementService.CARRIER_PRIVILEGES_LOST_TEARDOWN_DELAY_MS); mTestLooper.dispatchAll(); // Giving TEST_PACKAGE privileges again will restart the VCN (which will indicate ACTIVE |